diff --git a/compiler/rustc_abi/src/layout.rs b/compiler/rustc_abi/src/layout.rs index 1c6d0495ecf..3584eea0f14 100644 --- a/compiler/rustc_abi/src/layout.rs +++ b/compiler/rustc_abi/src/layout.rs @@ -816,15 +816,37 @@ where break; } }; - if let Some(pair) = common_prim { - // This is pretty conservative. We could go fancier - // by conflating things like i32 and u32, or even - // realising that (u8, u8) could just cohabit with - // u16 or even u32. - if pair != (prim, offset) { + if let Some((old_prim, common_offset)) = common_prim { + // All variants must be at the same offset + if offset != common_offset { common_prim = None; break; } + // This is pretty conservative. We could go fancier + // by realising that (u8, u8) could just cohabit with + // u16 or even u32. + let new_prim = match (old_prim, prim) { + // Allow all identical primitives. + (x, y) if x == y => x, + // Allow integers of the same size with differing signedness. + // We arbitrarily choose the signedness of the first variant. + (p @ Primitive::Int(x, _), Primitive::Int(y, _)) if x == y => p, + // Allow integers mixed with pointers of the same layout. + // We must represent this using a pointer, to avoid + // roundtripping pointers through ptrtoint/inttoptr. + (p @ Primitive::Pointer(_), i @ Primitive::Int(..)) + | (i @ Primitive::Int(..), p @ Primitive::Pointer(_)) + if p.size(dl) == i.size(dl) && p.align(dl) == i.align(dl) => + { + p + } + _ => { + common_prim = None; + break; + } + }; + // We may be updating the primitive here, for example from int->ptr. + common_prim = Some((new_prim, common_offset)); } else { common_prim = Some((prim, offset)); } diff --git a/tests/codegen/common_prim_int_ptr.rs b/tests/codegen/common_prim_int_ptr.rs new file mode 100644 index 00000000000..87fa89abb86 --- /dev/null +++ b/tests/codegen/common_prim_int_ptr.rs @@ -0,0 +1,51 @@ +//@ compile-flags: -O + +#![crate_type = "lib"] +#![feature(core_intrinsics)] + +// Tests that codegen works properly when enums like `Result>` +// are represented as `{ u64, ptr }`, i.e., for `Ok(123)`, `123` is stored +// as a pointer. + +// CHECK-LABEL: @insert_int +#[no_mangle] +pub fn insert_int(x: usize) -> Result> { + // CHECK: start: + // CHECK-NEXT: inttoptr i{{[0-9]+}} %x to ptr + // CHECK-NEXT: insertvalue + // CHECK-NEXT: ret { i{{[0-9]+}}, ptr } + Ok(x) +} + +// CHECK-LABEL: @insert_box +#[no_mangle] +pub fn insert_box(x: Box<()>) -> Result> { + // CHECK: start: + // CHECK-NEXT: insertvalue { i{{[0-9]+}}, ptr } + // CHECK-NEXT: ret + Err(x) +} + +// CHECK-LABEL: @extract_int +// CHECK-NOT: nonnull +// CHECK-SAME: (i{{[0-9]+}} {{[^,]+}} [[DISCRIMINANT:%[0-9]+]], ptr {{[^,]+}} [[PAYLOAD:%[0-9]+]]) +#[no_mangle] +pub unsafe fn extract_int(x: Result>) -> usize { + // CHECK: [[TEMP:%.+]] = ptrtoint ptr [[PAYLOAD]] to [[USIZE:i[0-9]+]] + // CHECK: ret [[USIZE]] [[TEMP]] + match x { + Ok(v) => v, + Err(_) => std::intrinsics::unreachable(), + } +} + +// CHECK-LABEL: @extract_box +// CHECK-SAME: (i{{[0-9]+}} {{[^,]+}} [[DISCRIMINANT:%[0-9]+]], ptr {{[^,]+}} [[PAYLOAD:%[0-9]+]]) +#[no_mangle] +pub unsafe fn extract_box(x: Result>) -> Box { + // CHECK: ret ptr [[PAYLOAD]] + match x { + Ok(_) => std::intrinsics::unreachable(), + Err(e) => e, + } +} diff --git a/tests/codegen/try_question_mark_nop.rs b/tests/codegen/try_question_mark_nop.rs index 58cd6ff233a..f6cdf955209 100644 --- a/tests/codegen/try_question_mark_nop.rs +++ b/tests/codegen/try_question_mark_nop.rs @@ -4,17 +4,41 @@ #![crate_type = "lib"] #![feature(try_blocks)] -// These are now NOPs in LLVM 15, presumably thanks to nikic's change mentioned in -// . -// Unfortunately, as of 2022-08-17 they're not yet nops for `u64`s nor `Option`. - use std::ops::ControlFlow::{self, Continue, Break}; +use std::ptr::NonNull; + +// CHECK-LABEL: @option_nop_match_32 +#[no_mangle] +pub fn option_nop_match_32(x: Option) -> Option { + // CHECK: start: + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: ret { i32, i32 } + match x { + Some(x) => Some(x), + None => None, + } +} + +// CHECK-LABEL: @option_nop_traits_32 +#[no_mangle] +pub fn option_nop_traits_32(x: Option) -> Option { + // CHECK: start: + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: ret { i32, i32 } + try { + x? + } +} // CHECK-LABEL: @result_nop_match_32 #[no_mangle] pub fn result_nop_match_32(x: Result) -> Result { - // CHECK: start - // CHECK-NEXT: ret i64 %0 + // CHECK: start: + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: ret { i32, i32 } match x { Ok(x) => Ok(x), Err(x) => Err(x), @@ -24,8 +48,60 @@ pub fn result_nop_match_32(x: Result) -> Result { // CHECK-LABEL: @result_nop_traits_32 #[no_mangle] pub fn result_nop_traits_32(x: Result) -> Result { - // CHECK: start - // CHECK-NEXT: ret i64 %0 + // CHECK: start: + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: ret { i32, i32 } + try { + x? + } +} + +// CHECK-LABEL: @result_nop_match_64 +#[no_mangle] +pub fn result_nop_match_64(x: Result) -> Result { + // CHECK: start: + // CHECK-NEXT: insertvalue { i64, i64 } + // CHECK-NEXT: insertvalue { i64, i64 } + // CHECK-NEXT: ret { i64, i64 } + match x { + Ok(x) => Ok(x), + Err(x) => Err(x), + } +} + +// CHECK-LABEL: @result_nop_traits_64 +#[no_mangle] +pub fn result_nop_traits_64(x: Result) -> Result { + // CHECK: start: + // CHECK-NEXT: insertvalue { i64, i64 } + // CHECK-NEXT: insertvalue { i64, i64 } + // CHECK-NEXT: ret { i64, i64 } + try { + x? + } +} + +// CHECK-LABEL: @result_nop_match_ptr +#[no_mangle] +pub fn result_nop_match_ptr(x: Result>) -> Result> { + // CHECK: start: + // CHECK-NEXT: insertvalue { i{{[0-9]+}}, ptr } + // CHECK-NEXT: insertvalue { i{{[0-9]+}}, ptr } + // CHECK-NEXT: ret + match x { + Ok(x) => Ok(x), + Err(x) => Err(x), + } +} + +// CHECK-LABEL: @result_nop_traits_ptr +#[no_mangle] +pub fn result_nop_traits_ptr(x: Result>) -> Result> { + // CHECK: start: + // CHECK-NEXT: insertvalue { i{{[0-9]+}}, ptr } + // CHECK-NEXT: insertvalue { i{{[0-9]+}}, ptr } + // CHECK-NEXT: ret try { x? } @@ -34,8 +110,10 @@ pub fn result_nop_traits_32(x: Result) -> Result { // CHECK-LABEL: @control_flow_nop_match_32 #[no_mangle] pub fn control_flow_nop_match_32(x: ControlFlow) -> ControlFlow { - // CHECK: start - // CHECK-NEXT: ret i64 %0 + // CHECK: start: + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: ret { i32, i32 } match x { Continue(x) => Continue(x), Break(x) => Break(x), @@ -45,8 +123,10 @@ pub fn control_flow_nop_match_32(x: ControlFlow) -> ControlFlow) -> ControlFlow { - // CHECK: start - // CHECK-NEXT: ret i64 %0 + // CHECK: start: + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: insertvalue { i32, i32 } + // CHECK-NEXT: ret { i32, i32 } try { x? } diff --git a/tests/ui/layout/enum-scalar-pair-int-ptr.rs b/tests/ui/layout/enum-scalar-pair-int-ptr.rs new file mode 100644 index 00000000000..a1aec094d80 --- /dev/null +++ b/tests/ui/layout/enum-scalar-pair-int-ptr.rs @@ -0,0 +1,24 @@ +//@ normalize-stderr-test "pref: Align\([1-8] bytes\)" -> "pref: $$PREF_ALIGN" +//@ normalize-stderr-test "Int\(I[0-9]+," -> "Int(I?," +//@ normalize-stderr-test "valid_range: 0..=[0-9]+" -> "valid_range: $$VALID_RANGE" + +//! Enum layout tests related to scalar pairs with an int/ptr common primitive. + +#![feature(rustc_attrs)] +#![feature(never_type)] +#![crate_type = "lib"] + +#[rustc_layout(abi)] +enum ScalarPairPointerWithInt { //~ERROR: abi: ScalarPair + A(usize), + B(Box<()>), +} + +// Negative test--ensure that pointers are not commoned with integers +// of a different size. (Assumes that no target has 8 bit pointers, which +// feels pretty safe.) +#[rustc_layout(abi)] +enum NotScalarPairPointerWithSmallerInt { //~ERROR: abi: Aggregate + A(u8), + B(Box<()>), +} diff --git a/tests/ui/layout/enum-scalar-pair-int-ptr.stderr b/tests/ui/layout/enum-scalar-pair-int-ptr.stderr new file mode 100644 index 00000000000..b25eda628cd --- /dev/null +++ b/tests/ui/layout/enum-scalar-pair-int-ptr.stderr @@ -0,0 +1,14 @@ +error: abi: ScalarPair(Initialized { value: Int(I?, false), valid_range: $VALID_RANGE }, Initialized { value: Pointer(AddressSpace(0)), valid_range: $VALID_RANGE }) + --> $DIR/enum-scalar-pair-int-ptr.rs:12:1 + | +LL | enum ScalarPairPointerWithInt { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: abi: Aggregate { sized: true } + --> $DIR/enum-scalar-pair-int-ptr.rs:21:1 + | +LL | enum NotScalarPairPointerWithSmallerInt { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/layout/enum.rs b/tests/ui/layout/enum.rs index bde8450b9d5..e0a7fc328df 100644 --- a/tests/ui/layout/enum.rs +++ b/tests/ui/layout/enum.rs @@ -16,3 +16,9 @@ enum UninhabitedVariantSpace { //~ERROR: size: Size(16 bytes) A, B([u8; 15], !), // make sure there is space being reserved for this field. } + +#[rustc_layout(abi)] +enum ScalarPairDifferingSign { //~ERROR: abi: ScalarPair + A(u8), + B(i8), +} diff --git a/tests/ui/layout/enum.stderr b/tests/ui/layout/enum.stderr index d6bc7780ce2..7f0b38d0a07 100644 --- a/tests/ui/layout/enum.stderr +++ b/tests/ui/layout/enum.stderr @@ -10,5 +10,11 @@ error: size: Size(16 bytes) LL | enum UninhabitedVariantSpace { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 2 previous errors +error: abi: ScalarPair(Initialized { value: Int(I8, false), valid_range: 0..=1 }, Initialized { value: Int(I8, false), valid_range: 0..=255 }) + --> $DIR/enum.rs:21:1 + | +LL | enum ScalarPairDifferingSign { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors