Change:
Currently they use a hardcoded discriminant, which is quite a lot of room considering that generators don't typically have billions of states. They should be able to use the smallest unsigned integer type that fits all states instead (so in the vast majority of cases).
The discriminant is also never marked with a valid range, meaning that it never provides any niches that could be used to store other values. Not sure if doing that has any drawbacks, but it seems like a somewhat easy size optimization on first glance.
Fixing this was way easier than I expected, but I'd appreciate help with benchmarking the runtime impact of the fix in . So if anyone knows any async/await benchmarks that are not I/O heavy (so that they mostly test CPU overhead of async/await), I'd be happy to run them.

Discussion:
// locals as part of the prefix. We compute the layout of all of // these fields at once to get optimal packing. let discr_index = substs.as_generator().prefix_tys(def_id, tcx).count(); <del> // FIXME(eddyb) set the correct vaidity range for the discriminant. let discr_layout = self.layout_of(substs.as_generator().discr_ty(tcx))?; let discr = match &discr_layout.abi { Abi::Scalar(s) => s.clone(), _ => bug!(), }; </del> <ins> // `info.variant_fields` already accounts for the reserved variants, so no need to add them. let max_discr = (info.variant_fields.len() - 1) as u128; let discr_int = Integer::fit_unsigned(max_discr); let discr_int_ty = discr_int.to_ty(tcx, false); let discr = Scalar { value: Primitive::Int(discr_int, false), valid_range: 0..=max_discr }; let discr_layout = self.tcx.intern_layout(LayoutDetails::scalar(self, discr.clone())); let discr_layout = TyLayout { ty: discr_int_ty, details: discr_layout }; </ins> let promoted_layouts = ineligible_locals .iter() .map(|local| subst_field(info.field_tys[local]))

} fn main() { <del> assert_eq!(1028, std::mem::size_of_val(&single())); assert_eq!(1032, std::mem::size_of_val(&single_with_noop())); assert_eq!(3084, std::mem::size_of_val(&joined())); assert_eq!(3084, std::mem::size_of_val(&joined_with_noop())); assert_eq!(7188, std::mem::size_of_val(&mixed_sizes())); </del> <ins> assert_eq!(1025, std::mem::size_of_val(&single())); assert_eq!(1026, std::mem::size_of_val(&single_with_noop())); assert_eq!(3078, std::mem::size_of_val(&joined())); assert_eq!(3079, std::mem::size_of_val(&joined_with_noop())); assert_eq!(7181, std::mem::size_of_val(&mixed_sizes())); </ins> }

} fn main() { <del> assert_eq!(8, std::mem::size_of_val(&single())); assert_eq!(12, std::mem::size_of_val(&single_with_noop())); assert_eq!(3084, std::mem::size_of_val(&joined())); assert_eq!(3084, std::mem::size_of_val(&joined_with_noop())); assert_eq!(3080, std::mem::size_of_val(&join_retval())); </del> <ins> assert_eq!(2, std::mem::size_of_val(&single())); assert_eq!(3, std::mem::size_of_val(&single_with_noop())); assert_eq!(3078, std::mem::size_of_val(&joined())); assert_eq!(3078, std::mem::size_of_val(&joined_with_noop())); assert_eq!(3074, std::mem::size_of_val(&join_retval())); </ins> }

fn main() { assert_eq!(2, std::mem::size_of_val(&base())); <del> assert_eq!(8, std::mem::size_of_val(&await1_level1())); assert_eq!(12, std::mem::size_of_val(&await2_level1())); assert_eq!(12, std::mem::size_of_val(&await3_level1())); assert_eq!(24, std::mem::size_of_val(&await3_level2())); assert_eq!(36, std::mem::size_of_val(&await3_level3())); assert_eq!(48, std::mem::size_of_val(&await3_level4())); assert_eq!(60, std::mem::size_of_val(&await3_level5())); </del> <ins> assert_eq!(3, std::mem::size_of_val(&await1_level1())); assert_eq!(4, std::mem::size_of_val(&await2_level1())); assert_eq!(5, std::mem::size_of_val(&await3_level1())); assert_eq!(8, std::mem::size_of_val(&await3_level2())); assert_eq!(11, std::mem::size_of_val(&await3_level3())); assert_eq!(14, std::mem::size_of_val(&await3_level4())); assert_eq!(17, std::mem::size_of_val(&await3_level5())); </ins> assert_eq!(1,   wait(base())); assert_eq!(1,   wait(await1_level1()));

<ins> //! Tests that generator discriminant sizes and ranges are chosen optimally and that they are //! reflected in the output of `mem::discriminant`. // run-pass #![feature(generators, generator_trait, core_intrinsics)] use std::intrinsics::discriminant_value; use std::marker::Unpin; use std::mem::size_of_val; use std::{cmp, ops::*}; macro_rules! yield25 { ($e:expr) => { yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; yield $e; }; } /// Yields 250 times. macro_rules! yield250 { () => { yield250!(()) }; ($e:expr) => { yield25!($e); yield25!($e); yield25!($e); yield25!($e); yield25!($e); yield25!($e); yield25!($e); yield25!($e); yield25!($e); yield25!($e); }; } fn cycle(gen: impl Generator<()> + Unpin, expected_max_discr: u64) { let mut gen = Box::pin(gen); let mut max_discr = 0; loop { max_discr = cmp::max(max_discr, discriminant_value(gen.as_mut().get_mut())); match gen.as_mut().resume(()) { GeneratorState::Yielded(_) => {} GeneratorState::Complete(_) => { assert_eq!(max_discr, expected_max_discr); return; } } } } fn main() { // Has only one invalid discr. value. let gen_u8_tiny_niche = || { || { // 3 reserved variants yield250!(); // 253 variants yield; // 254 yield; // 255 } }; // Uses all values in the u8 discriminant. let gen_u8_full = || { || { // 3 reserved variants yield250!(); // 253 variants yield; // 254 yield; // 255 yield; // 256 } }; // Barely needs a u16 discriminant. let gen_u16 = || { || { // 3 reserved variants yield250!(); // 253 variants yield; // 254 yield; // 255 yield; // 256 yield; // 257 } }; assert_eq!(size_of_val(&gen_u8_tiny_niche()), 1); assert_eq!(size_of_val(&Some(gen_u8_tiny_niche())), 1); // uses niche assert_eq!(size_of_val(&Some(Some(gen_u8_tiny_niche()))), 2); // cannot use niche anymore assert_eq!(size_of_val(&gen_u8_full()), 1); assert_eq!(size_of_val(&Some(gen_u8_full())), 2); // cannot use niche assert_eq!(size_of_val(&gen_u16()), 2); assert_eq!(size_of_val(&Some(gen_u16())), 2); // uses niche cycle(gen_u8_tiny_niche(), 254); cycle(gen_u8_full(), 255); cycle(gen_u16(), 256); } </ins>

// Neither of these generators have the resume arg live across the `yield`, so they should be // 4 Bytes in size (only storing the discriminant) <del> assert_eq!(size_of_val(&gen_copy), 4); assert_eq!(size_of_val(&gen_move), 4); </del> <ins> assert_eq!(size_of_val(&gen_copy), 1); assert_eq!(size_of_val(&gen_move), 1); </ins> }

} } <del> fn overlap_x_and_y() -> impl Generator<Yield = (), Return = ()>{ </del> <ins> fn overlap_x_and_y() -> impl Generator<Yield = (), Return = ()> { </ins> static || { let x = Foo([0; FOO_SIZE]); yield;

} fn main() { <del> assert_eq!(1028, std::mem::size_of_val(&move_before_yield())); assert_eq!(1032, std::mem::size_of_val(&move_before_yield_with_noop())); assert_eq!(2056, std::mem::size_of_val(&overlap_move_points())); assert_eq!(1032, std::mem::size_of_val(&overlap_x_and_y())); </del> <ins> assert_eq!(1025, std::mem::size_of_val(&move_before_yield())); assert_eq!(1026, std::mem::size_of_val(&move_before_yield_with_noop())); assert_eq!(2051, std::mem::size_of_val(&overlap_move_points())); assert_eq!(1026, std::mem::size_of_val(&overlap_x_and_y())); </ins> }