Auto merge of #155473 - scottmcm:tweak_niche_assignment, r=chenyukang,mati865

Prefer `-1` for `None`



Currently we pick "weird" numbers like `1114112` for `None::<char>`.  While that's not *wrong*, it's kinda *unnatural* -- a human wouldn't make that choice.

This PR instead picks `-1` for thinge like `None::<char>` -- like [clang's `WEOF`](63ae74b78a/libc/include/llvm-libc-macros/wchar-macros.h (L15)) -- and `None::<bool>` and such.

Any enums with more than one niched value (so not `Result` nor `Option`) remain as they were before.  Also we continue to use `0` when that's possible -- `-1` is only preferred when zero *isn't* possible.

---

Inspired when someone in discord posted an example like this <https://rust.godbolt.org/z/W94s9qdYW> and I thought it was odd that we're currently picking `-9223372036854775808` to be the value to store to mark an `Option<Vec<_>>` as `None`.  (Especially since that needs an 8-byte immediate on x64, and writing `-1` is only a 4-byte immediate.)
This commit is contained in:
bors
2026-04-26 17:06:25 +00:00
10 changed files with 296 additions and 18 deletions

View File

@@ -2054,8 +2054,7 @@ impl Niche {
assert!(size.bits() <= 128);
let max_value = size.unsigned_int_max();
let niche = v.end.wrapping_add(1)..v.start;
let available = niche.end.wrapping_sub(niche.start) & max_value;
let available = v.start.wrapping_sub(v.end).wrapping_sub(1) & max_value;
if count > available {
return None;
}
@@ -2083,7 +2082,17 @@ impl Niche {
Some((start, Scalar::Initialized { value, valid_range: v.with_end(end) }))
};
let distance_end_zero = max_value - v.end;
if v.start > v.end {
// FIXME: this ought to work for `bool` too, but that seems to be hitting a miscompilation
// <https://github.com/rust-lang/rust/pull/155473#issuecomment-4302036343>
if count == 1 && v != (WrappingRange { start: 0, end: 1 }) {
// We only need one, so just pick the one closest to zero.
// Not only does that obviously use zero if it's possible, but it also
// simplifies testing things like `Option<char>`, since looking for `-1`
// is easier than looking for `1114112` (and matches clang's `WEOF`).
let next_up = size.sign_extend(v.end.wrapping_add(1)).unsigned_abs();
let next_down = size.sign_extend(v.start.wrapping_sub(1)).unsigned_abs();
if next_down <= next_up { move_start(v) } else { move_end(v) }
} else if v.start > v.end {
// zero is unavailable because wrapping occurs
move_end(v)
} else if v.start <= distance_end_zero {

View File

@@ -2,6 +2,7 @@
//@ only-64bit
#![crate_type = "lib"]
#![feature(pattern_types, pattern_type_macro)]
use std::cmp::Ordering;
use std::num::NonZero;
@@ -123,3 +124,14 @@ fn make_fully_uninhabited_result(v: u32, n: Never) -> Result<(u32, Never), (Neve
}
enum Never {}
#[repr(transparent)]
struct NewtypeIndex(std::pat::pattern_type!(u32 is 0..=0xFFFFFF00));
#[no_mangle]
pub fn make_none_newtype_index() -> Option<NewtypeIndex> {
// CHECK-LABEL: @make_none_newtype_index
// CHECK-NEXT: start:
// CHECK-NEXT: ret i32 -1
None
}

View File

@@ -36,8 +36,8 @@ pub fn opt_bool_eq_discr(a: Option<bool>, b: Option<bool>) -> bool {
#[unsafe(no_mangle)]
pub fn opt_ord_eq_discr(a: Option<Ordering>, b: Option<Ordering>) -> bool {
// CHECK-LABEL: @opt_ord_eq_discr(
// CHECK: %[[A:.+]] = icmp ne i8 %a, 2
// CHECK: %[[B:.+]] = icmp eq i8 %b, 2
// CHECK: %[[A:.+]] = icmp ne i8 %a, -2
// CHECK: %[[B:.+]] = icmp eq i8 %b, -2
// CHECK: %[[R:.+]] = xor i1 %[[A]], %[[B]]
// CHECK: ret i1 %[[R]]
@@ -58,8 +58,8 @@ pub fn opt_nz32_eq_discr(a: Option<NonZero<u32>>, b: Option<NonZero<u32>>) -> bo
#[unsafe(no_mangle)]
pub fn opt_ac_eq_discr(a: Option<AC>, b: Option<AC>) -> bool {
// CHECK-LABEL: @opt_ac_eq_discr(
// CHECK: %[[A:.+]] = icmp ne i8 %a, -128
// CHECK: %[[B:.+]] = icmp eq i8 %b, -128
// CHECK: %[[A:.+]] = icmp ne i8 %a, -1
// CHECK: %[[B:.+]] = icmp eq i8 %b, -1
// CHECK: %[[R:.+]] = xor i1 %[[A]], %[[B]]
// CHECK: ret i1 %[[R]]

View File

@@ -81,7 +81,7 @@ use std::cmp::Ordering::{self, *};
#[no_mangle]
pub fn option_ordering_match(x: Option<Ordering>) -> char {
// CHECK: %[[RAW:.+]] = load i8, ptr %x
// CHECK: %[[IS_NONE:.+]] = icmp eq i8 %[[RAW]], 2
// CHECK: %[[IS_NONE:.+]] = icmp eq i8 %[[RAW]], -2
// CHECK: %[[OPT_DISCR:.+]] = select i1 %[[IS_NONE]], i64 0, i64 1
// CHECK: %[[OPT_DISCR_T:.+]] = trunc nuw i64 %[[OPT_DISCR]] to i1
// CHECK: br i1 %[[OPT_DISCR_T]], label %[[BB_SOME:.+]], label %[[BB_NONE:.+]]

View File

@@ -82,3 +82,14 @@ pub fn bool_eq(l: Option<bool>, r: Option<bool>) -> bool {
// CHECK-NEXT: ret i1
l == r
}
// CHECK-LABEL: @bool_ref_eq
#[no_mangle]
pub fn bool_ref_eq(l: &Option<bool>, r: &Option<bool>) -> bool {
// CHECK: start:
// CHECK-NEXT: load i8
// CHECK-NEXT: load i8
// CHECK-NEXT: icmp eq i8
// CHECK-NEXT: ret i1
l == r
}

View File

@@ -88,3 +88,9 @@ union EmptyUnion {} //~ ERROR: has an unknown layout
// (this error is never emitted to users).
#[rustc_dump_layout(debug)]
type TooGeneric<T> = T; //~ ERROR: does not have a fixed layout
#[rustc_dump_layout(debug)]
type OptBool = Option<bool>; //~ ERROR: layout_of
#[rustc_dump_layout(debug)]
type OptChar = Option<char>; //~ ERROR: layout_of

View File

@@ -614,6 +614,246 @@ error: the type `T` does not have a fixed layout
LL | type TooGeneric<T> = T;
| ^^^^^^^^^^^^^^^^^^
error: aborting due to 20 previous errors
error: layout_of(Option<bool>) = Layout {
size: Size(1 bytes),
align: AbiAlign {
abi: Align(1 bytes),
},
backend_repr: Scalar(
Initialized {
value: Int(
I8,
false,
),
valid_range: 0..=2,
},
),
fields: Arbitrary {
offsets: [
Size(0 bytes),
],
in_memory_order: [
0,
],
},
largest_niche: Some(
Niche {
offset: Size(0 bytes),
value: Int(
I8,
false,
),
valid_range: 0..=2,
},
),
uninhabited: false,
variants: Multiple {
tag: Initialized {
value: Int(
I8,
false,
),
valid_range: 0..=2,
},
tag_encoding: Niche {
untagged_variant: 1,
niche_variants: 0..=0,
niche_start: 2,
},
tag_field: 0,
variants: [
Layout {
size: Size(0 bytes),
align: AbiAlign {
abi: Align(1 bytes),
},
backend_repr: Memory {
sized: true,
},
fields: Arbitrary {
offsets: [],
in_memory_order: [],
},
largest_niche: None,
uninhabited: false,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: Align(1 bytes),
randomization_seed: $SEED,
},
Layout {
size: Size(1 bytes),
align: AbiAlign {
abi: Align(1 bytes),
},
backend_repr: Scalar(
Initialized {
value: Int(
I8,
false,
),
valid_range: 0..=1,
},
),
fields: Arbitrary {
offsets: [
Size(0 bytes),
],
in_memory_order: [
0,
],
},
largest_niche: Some(
Niche {
offset: Size(0 bytes),
value: Int(
I8,
false,
),
valid_range: 0..=1,
},
),
uninhabited: false,
variants: Single {
index: 1,
},
max_repr_align: None,
unadjusted_abi_align: Align(1 bytes),
randomization_seed: $SEED,
},
],
},
max_repr_align: None,
unadjusted_abi_align: Align(1 bytes),
randomization_seed: $SEED,
}
--> $DIR/debug.rs:93:1
|
LL | type OptBool = Option<bool>;
| ^^^^^^^^^^^^
error: layout_of(Option<char>) = Layout {
size: Size(4 bytes),
align: AbiAlign {
abi: Align(4 bytes),
},
backend_repr: Scalar(
Initialized {
value: Int(
I32,
false,
),
valid_range: (..=1114111) | (4294967295..),
},
),
fields: Arbitrary {
offsets: [
Size(0 bytes),
],
in_memory_order: [
0,
],
},
largest_niche: Some(
Niche {
offset: Size(0 bytes),
value: Int(
I32,
false,
),
valid_range: (..=1114111) | (4294967295..),
},
),
uninhabited: false,
variants: Multiple {
tag: Initialized {
value: Int(
I32,
false,
),
valid_range: (..=1114111) | (4294967295..),
},
tag_encoding: Niche {
untagged_variant: 1,
niche_variants: 0..=0,
niche_start: 4294967295,
},
tag_field: 0,
variants: [
Layout {
size: Size(0 bytes),
align: AbiAlign {
abi: Align(1 bytes),
},
backend_repr: Memory {
sized: true,
},
fields: Arbitrary {
offsets: [],
in_memory_order: [],
},
largest_niche: None,
uninhabited: false,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: Align(1 bytes),
randomization_seed: $SEED,
},
Layout {
size: Size(4 bytes),
align: AbiAlign {
abi: Align(4 bytes),
},
backend_repr: Scalar(
Initialized {
value: Int(
I32,
false,
),
valid_range: 0..=1114111,
},
),
fields: Arbitrary {
offsets: [
Size(0 bytes),
],
in_memory_order: [
0,
],
},
largest_niche: Some(
Niche {
offset: Size(0 bytes),
value: Int(
I32,
false,
),
valid_range: 0..=1114111,
},
),
uninhabited: false,
variants: Single {
index: 1,
},
max_repr_align: None,
unadjusted_abi_align: Align(4 bytes),
randomization_seed: $SEED,
},
],
},
max_repr_align: None,
unadjusted_abi_align: Align(4 bytes),
randomization_seed: $SEED,
}
--> $DIR/debug.rs:96:1
|
LL | type OptChar = Option<char>;
| ^^^^^^^^^^^^
error: aborting due to 22 previous errors
For more information about this error, try `rustc --explain E0277`.

View File

@@ -370,7 +370,7 @@ error: layout_of(Result<[u32; 0], Packed<U16IsZero>>) = Layout {
I16,
false,
),
valid_range: 0..=1,
valid_range: (..=0) | (65535..),
},
),
uninhabited: false,
@@ -380,12 +380,12 @@ error: layout_of(Result<[u32; 0], Packed<U16IsZero>>) = Layout {
I16,
false,
),
valid_range: 0..=1,
valid_range: (..=0) | (65535..),
},
tag_encoding: Niche {
untagged_variant: 1,
niche_variants: 0..=0,
niche_start: 1,
niche_start: 65535,
},
tag_field: 0,
variants: [

View File

@@ -57,7 +57,7 @@ error: layout_of(Option<(*const ()) is !null>) = Layout {
0,
),
),
valid_range: (..=0) | (1..),
valid_range: 0..=18446744073709551615,
},
),
fields: Arbitrary {
@@ -77,7 +77,7 @@ error: layout_of(Option<(*const ()) is !null>) = Layout {
0,
),
),
valid_range: (..=0) | (1..),
valid_range: 0..=18446744073709551615,
},
tag_encoding: Niche {
untagged_variant: 1,

View File

@@ -99,7 +99,7 @@ error: layout_of(Option<(u32) is 1..>) = Layout {
I32,
false,
),
valid_range: (..=0) | (1..),
valid_range: 0..=4294967295,
},
),
fields: Arbitrary {
@@ -118,7 +118,7 @@ error: layout_of(Option<(u32) is 1..>) = Layout {
I32,
false,
),
valid_range: (..=0) | (1..),
valid_range: 0..=4294967295,
},
tag_encoding: Niche {
untagged_variant: 1,
@@ -210,7 +210,7 @@ error: layout_of(Option<NonZero<u32>>) = Layout {
I32,
false,
),
valid_range: (..=0) | (1..),
valid_range: 0..=4294967295,
},
),
fields: Arbitrary {
@@ -229,7 +229,7 @@ error: layout_of(Option<NonZero<u32>>) = Layout {
I32,
false,
),
valid_range: (..=0) | (1..),
valid_range: 0..=4294967295,
},
tag_encoding: Niche {
untagged_variant: 1,