Lower the LLVM IR output of the generated output by bolshoytoster · Pull Request #492 · bitflags/bitflags · GitHub
Skip to content

Lower the LLVM IR output of the generated output - #492

Merged
KodrAus merged 6 commits into
bitflags:mainfrom
bolshoytoster:main
Jul 12, 2026
Merged

KodrAus merged 6 commits into
bitflags:mainfrom
bolshoytoster:main

Conversation

@bolshoytoster

Copy link
Copy Markdown
Contributor

This PR lowers the LLVM IR (and MIR) sizes of the generated all and from_name functions. (#491)

I have wrapped all in const {}, which causes it to be evaluated early on. I also decided to simplify the function to reduce what it's evaluating (before:

fn _::<impl at src/lib.rs:658:9: 658:29>::all() -> InternalBitFlags {
    let mut _0: _::InternalBitFlags;
    let mut _1: u64;
    let mut _4: &bitflags::Flag<FeaturesWGPU>;
    let _5: &[bitflags::Flag<FeaturesWGPU>];
    let _6: usize;
    let mut _7: bool;
    let mut _8: u64;
    let mut _10: &bitflags::Flag<FeaturesWGPU>;
    let _11: &[bitflags::Flag<FeaturesWGPU>];
    let _12: usize;
    let mut _13: bool;
    let mut _14: u64;
    let mut _16: &bitflags::Flag<FeaturesWGPU>;
    let _17: &[bitflags::Flag<FeaturesWGPU>];
    let _18: usize;
    let mut _19: bool;
    let mut _20: u64;
    scope 1 {
        debug truncated => _1;
        let mut _2: usize;
        scope 2 {
            debug i => _2;
            let _3: u64;
            let _9: u64;
            let _15: u64;
            scope 3 {
                debug flag => _3;
            }
            scope 4 {
                debug flag => _9;
            }
            scope 5 {
                debug flag => _15;
            }
            scope 6 {
            }
            scope 7 (inlined Flag::<FeaturesWGPU>::value) {
            }
            scope 8 (inlined _::<impl FeaturesWGPU>::bits) {
                scope 9 (inlined InternalBitFlags::bits) {
                }
            }
            scope 10 (inlined Flag::<FeaturesWGPU>::value) {
            }
            scope 11 (inlined _::<impl FeaturesWGPU>::bits) {
                scope 12 (inlined InternalBitFlags::bits) {
                }
            }
            scope 13 (inlined Flag::<FeaturesWGPU>::value) {
            }
            scope 14 (inlined _::<impl FeaturesWGPU>::bits) {
                scope 15 (inlined InternalBitFlags::bits) {
                }
            }
        }
    }

    bb0: {
        _1 = const 0_u64;
        StorageLive(_2);
        _2 = const 0_usize;
        StorageLive(_3);
        StorageLive(_5);
        _5 = const <FeaturesWGPU as bitflags::Flags>::FLAGS;
        StorageLive(_6);
        _6 = copy _2;
        _7 = Lt(copy _6, const 3_usize);
        assert(move _7, "index out of bounds: the length is {} but the index is {}", const 3_usize, copy _6) -> [success: bb1, unwind continue];
    }

    bb1: {
        _4 = &(*_5)[_6];
        _3 = copy ((((*_4).1: FeaturesWGPU).0: _::InternalBitFlags).0: u64);
        StorageDead(_6);
        StorageDead(_5);
        StorageLive(_8);
        _8 = copy _1;
        _1 = BitOr(move _8, copy _3);
        StorageDead(_8);
        _2 = Add(copy _2, const 1_usize);
        StorageDead(_3);
        StorageLive(_9);
        StorageLive(_11);
        _11 = const <FeaturesWGPU as bitflags::Flags>::FLAGS;
        StorageLive(_12);
        _12 = copy _2;
        _13 = Lt(copy _12, const 3_usize);
        assert(move _13, "index out of bounds: the length is {} but the index is {}", const 3_usize, copy _12) -> [success: bb2, unwind continue];
    }

    bb2: {
        _10 = &(*_11)[_12];
        _9 = copy ((((*_10).1: FeaturesWGPU).0: _::InternalBitFlags).0: u64);
        StorageDead(_12);
        StorageDead(_11);
        StorageLive(_14);
        _14 = copy _1;
        _1 = BitOr(move _14, copy _9);
        StorageDead(_14);
        _2 = Add(copy _2, const 1_usize);
        StorageDead(_9);
        StorageLive(_15);
        StorageLive(_17);
        _17 = const <FeaturesWGPU as bitflags::Flags>::FLAGS;
        StorageLive(_18);
        _18 = copy _2;
        _19 = Lt(copy _18, const 3_usize);
        assert(move _19, "index out of bounds: the length is {} but the index is {}", const 3_usize, copy _18) -> [success: bb3, unwind continue];
    }

    bb3: {
        _16 = &(*_17)[_18];
        _15 = copy ((((*_16).1: FeaturesWGPU).0: _::InternalBitFlags).0: u64);
        StorageDead(_18);
        StorageDead(_17);
        StorageLive(_20);
        _20 = copy _1;
        _1 = BitOr(move _20, copy _15);
        StorageDead(_20);
        _2 = Add(copy _2, const 1_usize);
        StorageDead(_15);
        _0 = InternalBitFlags(move _1);
        StorageDead(_2);
        return;
    }
}

after:

fn _::<impl at src/lib.rs:658:9: 658:29>::all() -> InternalBitFlags {
    let mut _0: _::InternalBitFlags;
    let mut _1: u64;
    scope 1 {
        debug truncated => _1;
    }

    bb0: {
        _1 = const 0_u64;
        _1 = BitOr(copy _1, const 1_u64);
        _1 = BitOr(copy _1, const 2_u64);
        _1 = BitOr(copy _1, const 4_u64);
        _0 = InternalBitFlags(move _1);
        return;
    }
}

)

I have measured the bloat by adding this file (emulating wgpu's 58-flag FeaturesWGPU) as examples/all_bloat.rs:

bitflags::bitflags! {
	struct FeaturesWGPU: u64 {
		const SHADER_FLOAT32_ATOMIC = 1 << 0;

		const TEXTURE_FORMAT_16BIT_NORM = 1 << 1;

		const TEXTURE_COMPRESSION_ASTC_HDR = 1 << 2;

		const TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES = 1 << 3;

		const PIPELINE_STATISTICS_QUERY = 1 << 4;

		const TIMESTAMP_QUERY_INSIDE_ENCODERS = 1 << 5;

		const TIMESTAMP_QUERY_INSIDE_PASSES = 1 << 6;

		const MAPPABLE_PRIMARY_BUFFERS = 1 << 7;

		const TEXTURE_BINDING_ARRAY = 1 << 8;

		const BUFFER_BINDING_ARRAY = 1 << 9;

		const STORAGE_RESOURCE_BINDING_ARRAY = 1 << 10;

		const SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING = 1 << 11;

		const STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING = 1 << 12;

		const PARTIALLY_BOUND_BINDING_ARRAY = 1 << 13;

		const MULTI_DRAW_INDIRECT_COUNT = 1 << 15;

		const ADDRESS_MODE_CLAMP_TO_ZERO = 1 << 17;

		const ADDRESS_MODE_CLAMP_TO_BORDER = 1 << 18;

		const POLYGON_MODE_LINE = 1 << 19;

		const POLYGON_MODE_POINT = 1 << 20;

		const CONSERVATIVE_RASTERIZATION = 1 << 21;

		const VERTEX_WRITABLE_STORAGE = 1 << 22;

		const CLEAR_TEXTURE = 1 << 23;

		const MULTIVIEW = 1 << 26;

		const VERTEX_ATTRIBUTE_64BIT = 1 << 27;

		const TEXTURE_ATOMIC = 1 << 28;

		const TEXTURE_FORMAT_NV12 = 1 << 29;

		const TEXTURE_FORMAT_P010 = 1 << 30;

		const EXTERNAL_TEXTURE = 1 << 31;

		const EXPERIMENTAL_RAY_QUERY = 1 << 32;

		const SHADER_F64 = 1 << 33;

		const SHADER_I16 = 1 << 34;

		const SHADER_EARLY_DEPTH_TEST = 1 << 36;

		const SHADER_INT64 = 1 << 37;

		const SUBGROUP = 1 << 38;

		const SUBGROUP_VERTEX = 1 << 39;

		const SUBGROUP_BARRIER = 1 << 40;

		const PIPELINE_CACHE = 1 << 41;

		const SHADER_INT64_ATOMIC_MIN_MAX = 1 << 42;

		const SHADER_INT64_ATOMIC_ALL_OPS = 1 << 43;

		const VULKAN_GOOGLE_DISPLAY_TIMING = 1 << 44;

		const VULKAN_EXTERNAL_MEMORY_WIN32 = 1 << 45;

		const TEXTURE_INT64_ATOMIC = 1 << 46;

		const UNIFORM_BUFFER_BINDING_ARRAYS = 1 << 47;

		const EXPERIMENTAL_MESH_SHADER = 1 << 48;

		const EXPERIMENTAL_RAY_HIT_VERTEX_RETURN = 1 << 49;

		const EXPERIMENTAL_MESH_SHADER_MULTIVIEW = 1 << 50;

		const EXTENDED_ACCELERATION_STRUCTURE_VERTEX_FORMATS = 1 << 51;

		const PASSTHROUGH_SHADERS = 1 << 52;

		const SHADER_BARYCENTRICS = 1 << 53;

		const SELECTIVE_MULTIVIEW = 1 << 54;

		const EXPERIMENTAL_MESH_SHADER_POINTS = 1 << 55;

		const MULTISAMPLE_ARRAY = 1 << 56;

		const EXPERIMENTAL_COOPERATIVE_MATRIX = 1 << 57;

		const SHADER_PER_VERTEX = 1 << 58;

		const SHADER_DRAW_INDEX = 1 << 59;

		const ACCELERATION_STRUCTURE_BINDING_ARRAY = 1 << 60;

		const MEMORY_DECORATION_COHERENT = 1 << 61;

		const MEMORY_DECORATION_VOLATILE = 1 << 62;
	}
}

fn main() {
	FeaturesWGPU::all();
}

and running cargo llvm-lines --example all_bloat/cargo llvm-lines --example all_bloat --release.

I got the MIR output with cargo asm --example all_bloat --mir "fn _::<impl at src/lib.rs:658:9: 658:29>::all() -> InternalBitFlags" 0

@bolshoytoster

bolshoytoster commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor Author

@KodrAus

KodrAus commented Jul 3, 2026

Copy link
Copy Markdown
Member

Thanks for looking at this @bolshoytoster! The internal/public type split is so that if you add #[derive]s to your generated flags type it can use a flags-aware implementation instead of just treating the value as an integer. Referencing Self in flags consts is pretty common, so we need that to keep working. Do we still get an improvement in codegen with just the const-wrapping?

@bolshoytoster

Copy link
Copy Markdown
Contributor Author

Thanks for the explaination. There's still an improvement from just the const wrapping, and there's still an MIR benefit in using $PublicBitFlags::$Flag.bits() instead of $crate::Flags>::FLAGS[i].value().bits(). The only difference between this and the previous commit is that now the compiler has to inline/evaluate bits(), which is probably cheap.

@bolshoytoster

Copy link
Copy Markdown
Contributor Author

I'm not really sure what to do about the MSRV test failure. const {} blocks aren't supported in the MSRV. I think there are 3 options:

  • Just drop the PR and ignore the bloat
  • Move the implementation of all into the public type, so at least it can get the benefit of changing <$PublicBitFlags as $crate::Flags>::FLAGS[i] to $value
  • Duplicate the function body and use #[cfg()] to pick either the const or old one

@bolshoytoster

Copy link
Copy Markdown
Contributor Author

It looks like I could also use an inner const:

fn all() {
	const ALL: $BitFlags = {
		let mut truncated = <$T as $crate::Bits>::EMPTY;
		let mut _i = 0;

		$(
			$crate::__bitflags_expr_safe_attrs!(
				$(#[$inner $($args)*])*
				{{
					truncated |= <$PublicBitFlags as $crate::Flags>::FLAGS[_i]
						.value().bits();
					_i += 1;
				}}
			);
		)*

		$BitFlags(truncated)
	};

	ALL
}

But that seems a little ugly.

@KodrAus

KodrAus commented Jul 10, 2026

Copy link
Copy Markdown
Member

Thanks for working on this @bolshoytoster. I think using an inner const might be the best approach here. We are pretty conservative with our MSRV in bitflags.

@bolshoytoster

Copy link
Copy Markdown
Contributor Author

The last issue is the compile-fail tests are giving different error messages. It looks like the difference is just semantic (s/constant function/constants/):

- error[E0015]: cannot call non-const operator in constant functions
     --> tests/compile-fail/bitflags_custom_bits.rs:132:1
      |
  132 | / bitflags! {
  133 | |     struct Flags128: MyInt {
  134 | |         const A = MyInt(0b0000_0001u8);
  135 | |         const B = MyInt(0b0000_0010u8);
  ...   |
  138 | | }
      | |_^
      |
  note: impl defined here, but it is not `const`
-    --> tests/compile-fail/bitflags_custom_bits.rs:42:1
      |
-  42 | impl BitOr for MyInt {
-     | ^^^^^^^^^^^^^^^^^^^^
-     = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
+ error[E0015]: cannot call non-const operator in constants
     --> tests/compile-fail/bitflags_custom_bits.rs:132:1
      |
  132 | / bitflags! {
  133 | |     struct Flags128: MyInt {
  134 | |         const A = MyInt(0b0000_0001u8);
  135 | |         const B = MyInt(0b0000_0010u8);
  ...   |
  138 | | }
      | |_^
      |
  note: impl defined here, but it is not `const`
+    --> tests/compile-fail/bitflags_custom_bits.rs:64:1
      |
+  64 | impl BitOrAssign for MyInt {
+     | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+     = note: calls in constants are limited to constant functions, tuple structs and tuple variants

Can I bless it?

@KodrAus

KodrAus commented Jul 11, 2026

Copy link
Copy Markdown
Member

Looks good! Yeh the compile fail tests regularly change anyways just from changes in rustc diagnostics.

@bolshoytoster

Copy link
Copy Markdown
Contributor Author

@KodrAus KodrAus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bolshoytoster!

@KodrAus
KodrAus merged commit e077c4a into bitflags:main Jul 12, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants