From 0fc6688ab5e54bdb970881a8997d86f650445988 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sat, 20 Jun 2026 19:15:31 -0700 Subject: [PATCH 1/2] Add a test for current read/write unaligned behaviour --- tests/codegen-llvm/read_write_unaligned.rs | 71 +++++++++++++ tests/mir-opt/pre-codegen/unaligned.rs | 37 +++++++ ...d_copy_generic.runtime-optimized.after.mir | 99 +++++++++++++++++++ ...ed_copy_manual.runtime-optimized.after.mir | 34 +++++++ 4 files changed, 241 insertions(+) create mode 100644 tests/codegen-llvm/read_write_unaligned.rs create mode 100644 tests/mir-opt/pre-codegen/unaligned.rs create mode 100644 tests/mir-opt/pre-codegen/unaligned.unaligned_copy_generic.runtime-optimized.after.mir create mode 100644 tests/mir-opt/pre-codegen/unaligned.unaligned_copy_manual.runtime-optimized.after.mir diff --git a/tests/codegen-llvm/read_write_unaligned.rs b/tests/codegen-llvm/read_write_unaligned.rs new file mode 100644 index 0000000000000..beba4400dc54a --- /dev/null +++ b/tests/codegen-llvm/read_write_unaligned.rs @@ -0,0 +1,71 @@ +//@ compile-flags: -Copt-level=3 +//@ only-64bit + +#![crate_type = "lib"] + +use std::num::NonZero; +use std::ptr::NonNull; + +// CHECK-LABEL: nonnull ptr @read_unaligned_ptr(ptr{{.+}}%ptr) +#[no_mangle] +unsafe fn read_unaligned_ptr(ptr: *const NonNull) -> NonNull { + // CHECK: start: + // CHECK-NEXT: [[TEMP:%.+]] = load ptr, ptr %ptr, align 1 + // CHECK-SAME: !nonnull + // CHECK-SAME: !noundef + // CHECK-NEXT: ret ptr [[TEMP]] + ptr.read_unaligned() +} + +// CHECK-LABEL: range(i16 1, 0) i16 @read_unaligned_i16(ptr{{.+}}%ptr) +#[no_mangle] +unsafe fn read_unaligned_i16(ptr: *const NonZero) -> NonZero { + // CHECK: start: + // CHECK-NEXT: [[TEMP:%.+]] = load i16, ptr %ptr, align 1 + // CHECK-NOT: !noundef + // CHECK-NOT: !range + // CHECK-NEXT: ret i16 [[TEMP]] + ptr.read_unaligned() +} + +// CHECK-LABEL: void @typed_copy_unaligned_i32(ptr{{.+}}%src, ptr{{.+}}%dst) +#[no_mangle] +unsafe fn typed_copy_unaligned_i32(src: *const NonZero, dst: *mut NonZero) { + // CHECK: start: + // CHECK-NEXT: [[TEMP:%.+]] = load i32, ptr %src, align 1 + // CHECK-NOT: !noundef + // CHECK-NOT: !range + // CHECK-NEXT: store i32 [[TEMP]], ptr %dst, align 1 + // CHECK-NEXT: ret void + dst.write_unaligned(src.read_unaligned()) +} + +// CHECK-LABEL: void @write_unaligned_i64(ptr{{.+}}%ptr, i64{{.+}}%val) +#[no_mangle] +unsafe fn write_unaligned_i64(ptr: *mut NonZero, val: NonZero) { + // CHECK: start: + // CHECK-NEXT: store i64 %val, ptr %ptr, align 1 + // CHECK-NEXT: ret void + ptr.write_unaligned(val) +} + +#[repr(align(128))] +struct HugeBuffer([u64; 1 << 10]); + +// CHECK-LABEL: void @read_unaligned_huge(ptr{{.+}}%_0, ptr{{.+}}%ptr) +#[no_mangle] +unsafe fn read_unaligned_huge(ptr: *const HugeBuffer) -> HugeBuffer { + // CHECK: start: + // CHECK-NEXT: call void @llvm.memcpy{{.+}} align 128 dereferenceable(8192) %_0, {{.+}} align 1 dereferenceable(8192) %ptr, i64 8192, + // CHECK-NEXT: ret void + ptr.read_unaligned() +} + +// CHECK-LABEL: void @write_unaligned_huge(ptr{{.+}}%ptr, ptr{{.+}}%val) +#[no_mangle] +unsafe fn write_unaligned_huge(ptr: *mut HugeBuffer, val: HugeBuffer) { + // CHECK: start: + // CHECK-NEXT: call void @llvm.memcpy{{.+}} align 1 dereferenceable(8192) %ptr, {{.+}} align 128 dereferenceable(8192) %val, i64 8192, + // CHECK-NEXT: ret void + ptr.write_unaligned(val) +} diff --git a/tests/mir-opt/pre-codegen/unaligned.rs b/tests/mir-opt/pre-codegen/unaligned.rs new file mode 100644 index 0000000000000..70fbc373ca499 --- /dev/null +++ b/tests/mir-opt/pre-codegen/unaligned.rs @@ -0,0 +1,37 @@ +//@ compile-flags: -O -Zmir-opt-level=2 +//@ ignore-std-debug-assertions (there's one in `ptr::read` via `MaybeUninit::assume_init`) + +#![crate_type = "lib"] + +// EMIT_MIR unaligned.unaligned_copy_manual.runtime-optimized.after.mir +pub unsafe fn unaligned_copy_manual(src: *const u128, dst: *mut u128) { + #[repr(packed)] + struct Packed(T); + + // CHECK-LABEL: fn unaligned_copy_manual(_1: *const u128, _2: *mut u128) -> () + // CHECK: [[SRCU:_.+]] = copy _1 as *const unaligned_copy_manual::Packed (PtrToPtr); + // CHECK: [[DSTU:_.+]] = copy _2 as *mut unaligned_copy_manual::Packed (PtrToPtr); + // CHECK: [[TEMP:_.+]] = copy ((*[[SRCU]]).0: u128); + // ((*[[DSTU]]).0: u128) = move [[TEMP]]; + let src = src.cast::>(); + let dst = dst.cast::>(); + unsafe { (*dst).0 = (*src).0 }; +} + +// EMIT_MIR unaligned.unaligned_copy_generic.runtime-optimized.after.mir +pub unsafe fn unaligned_copy_generic(src: *const T, dst: *mut T) { + // CHECK-LABEL: fn unaligned_copy_generic( + // CHECK: debug src => _1; + // CHECK: debug dst => _2; + // CHECK: debug val => [[VAL:_.+]]; + // CHECK: copy _1 as *const u8 (PtrToPtr); + // CHECK: &raw mut + // CHECK: copy_nonoverlapping{{.+}}count = const ::SIZE + // CHECK: &raw const + // CHECK: copy _2 as *mut u8 (PtrToPtr); + // CHECK: copy_nonoverlapping{{.+}}count = const ::SIZE + unsafe { + let val = std::ptr::read_unaligned(src); + std::ptr::write_unaligned(dst, val); + } +} diff --git a/tests/mir-opt/pre-codegen/unaligned.unaligned_copy_generic.runtime-optimized.after.mir b/tests/mir-opt/pre-codegen/unaligned.unaligned_copy_generic.runtime-optimized.after.mir new file mode 100644 index 0000000000000..ad474ba1edb73 --- /dev/null +++ b/tests/mir-opt/pre-codegen/unaligned.unaligned_copy_generic.runtime-optimized.after.mir @@ -0,0 +1,99 @@ +// MIR for `unaligned_copy_generic` after runtime-optimized + +fn unaligned_copy_generic(_1: *const T, _2: *mut T) -> () { + debug src => _1; + debug dst => _2; + let mut _0: (); + let _9: T; + let mut _10: T; + scope 1 { + debug val => _9; + scope 14 (inlined #[track_caller] write_unaligned::) { + let mut _11: *const T; + let mut _12: *const u8; + let mut _13: *mut u8; + scope 15 (inlined std::mem::size_of::) { + } + scope 16 (inlined std::ptr::copy_nonoverlapping::) { + scope 17 (inlined core::ub_checks::check_language_ub) { + scope 18 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + scope 19 (inlined std::mem::size_of::) { + } + scope 20 (inlined std::mem::align_of::) { + } + } + } + } + scope 2 (inlined #[track_caller] read_unaligned::) { + let mut _3: std::mem::MaybeUninit; + let mut _4: *const u8; + let mut _6: *mut u8; + let mut _7: std::mem::MaybeUninit; + scope 3 { + scope 5 (inlined MaybeUninit::::as_mut_ptr) { + let mut _5: *mut std::mem::MaybeUninit; + } + scope 6 (inlined std::mem::size_of::) { + } + scope 7 (inlined std::ptr::copy_nonoverlapping::) { + scope 8 (inlined core::ub_checks::check_language_ub) { + scope 9 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + scope 10 (inlined std::mem::size_of::) { + } + scope 11 (inlined std::mem::align_of::) { + } + } + scope 12 (inlined #[track_caller] MaybeUninit::::assume_init) { + let _8: (); + scope 13 (inlined transmute_neo::, T>) { + } + } + } + scope 4 (inlined MaybeUninit::::uninit) { + } + } + + bb0: { + StorageLive(_9); + StorageLive(_5); + StorageLive(_3); + _3 = MaybeUninit:: { uninit: const () }; + StorageLive(_4); + _4 = copy _1 as *const u8 (PtrToPtr); + StorageLive(_6); + _5 = &raw mut _3; + _6 = copy _5 as *mut u8 (PtrToPtr); + copy_nonoverlapping(dst = copy _6, src = copy _4, count = const ::SIZE); + StorageDead(_6); + StorageDead(_4); + StorageLive(_7); + _7 = move _3; + _8 = assert_inhabited::() -> [return: bb1, unwind unreachable]; + } + + bb1: { + _9 = copy _7 as T (Transmute); + StorageDead(_7); + StorageDead(_3); + StorageDead(_5); + StorageLive(_10); + _10 = move _9; + StorageLive(_11); + StorageLive(_12); + _11 = &raw const _10; + _12 = copy _11 as *const u8 (PtrToPtr); + StorageLive(_13); + _13 = copy _2 as *mut u8 (PtrToPtr); + copy_nonoverlapping(dst = copy _13, src = copy _12, count = const ::SIZE); + StorageDead(_13); + StorageDead(_12); + StorageDead(_11); + StorageDead(_10); + StorageDead(_9); + return; + } +} diff --git a/tests/mir-opt/pre-codegen/unaligned.unaligned_copy_manual.runtime-optimized.after.mir b/tests/mir-opt/pre-codegen/unaligned.unaligned_copy_manual.runtime-optimized.after.mir new file mode 100644 index 0000000000000..450df935a0fb3 --- /dev/null +++ b/tests/mir-opt/pre-codegen/unaligned.unaligned_copy_manual.runtime-optimized.after.mir @@ -0,0 +1,34 @@ +// MIR for `unaligned_copy_manual` after runtime-optimized + +fn unaligned_copy_manual(_1: *const u128, _2: *mut u128) -> () { + debug src => _1; + debug dst => _2; + let mut _0: (); + let _3: *const unaligned_copy_manual::Packed; + let mut _5: u128; + scope 1 { + debug src => _3; + let _4: *mut unaligned_copy_manual::Packed; + scope 2 { + debug dst => _4; + } + scope 4 (inlined std::ptr::mut_ptr::::cast::>) { + } + } + scope 3 (inlined std::ptr::const_ptr::::cast::>) { + } + + bb0: { + StorageLive(_3); + _3 = copy _1 as *const unaligned_copy_manual::Packed (PtrToPtr); + StorageLive(_4); + _4 = copy _2 as *mut unaligned_copy_manual::Packed (PtrToPtr); + StorageLive(_5); + _5 = copy ((*_3).0: u128); + ((*_4).0: u128) = move _5; + StorageDead(_5); + StorageDead(_4); + StorageDead(_3); + return; + } +} From 3a9c74d36180c2d93618ecc2e976ed59e510946f Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Tue, 23 Jun 2026 21:44:03 -0700 Subject: [PATCH 2/2] Implement `ptr::{read,write}_aligned` via `repr(packed)` --- library/core/src/ptr/mod.rs | 43 ++++++--- tests/mir-opt/pre-codegen/unaligned.rs | 16 ++-- ...d_copy_generic.runtime-optimized.after.mir | 92 +++++-------------- 3 files changed, 63 insertions(+), 88 deletions(-) diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 593011edecf27..b4d47205f2fea 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -1810,16 +1810,24 @@ pub const unsafe fn read(src: *const T) -> T { #[track_caller] #[rustc_diagnostic_item = "ptr_read_unaligned"] pub const unsafe fn read_unaligned(src: *const T) -> T { - let mut tmp = MaybeUninit::::uninit(); + // Always true thanks to the repr, but to demonstrate + const { + assert!(mem::offset_of!(Packed::, 0) == 0); + assert!(size_of::() == size_of::>()); + } + + let src = src.cast::>(); // SAFETY: the caller must guarantee that `src` is valid for reads. - // `src` cannot overlap `tmp` because `tmp` was just allocated on - // the stack as a separate allocation. + // Reading it as `Packed` instead of `T` reads those same bytes because + // it's the same size (thus zero offset), but with alignment 1 instead. // - // Also, since we just wrote a valid value into `tmp`, it is guaranteed - // to be properly initialized. + // Similarly, because it's the same bytes it's sound to transmute from the + // `Packed` to `T`. Transmute is a value-based (not a place-based) + // operation that doesn't care about alignment. unsafe { - copy_nonoverlapping(src as *const u8, tmp.as_mut_ptr() as *mut u8, size_of::()); - tmp.assume_init() + let packed = read(src); + // Can't just destructure because that's not allowed in const fn + mem::transmute_neo(packed) } } @@ -2012,14 +2020,18 @@ pub const unsafe fn write(dst: *mut T, src: T) { #[rustc_diagnostic_item = "ptr_write_unaligned"] #[track_caller] pub const unsafe fn write_unaligned(dst: *mut T, src: T) { - // SAFETY: the caller must guarantee that `dst` is valid for writes. - // `dst` cannot overlap `src` because the caller has mutable access - // to `dst` while `src` is owned by this function. - unsafe { - copy_nonoverlapping((&raw const src) as *const u8, dst as *mut u8, size_of::()); - // We are calling the intrinsic directly to avoid function calls in the generated code. - intrinsics::forget(src); + // Always true thanks to the repr, but to demonstrate + const { + assert!(mem::offset_of!(Packed::, 0) == 0); + assert!(size_of::() == size_of::>()); } + + let dst = dst.cast::>(); + let src = Packed(src); + // SAFETY: the caller must guarantee that `dst` is valid for writes. + // Writing it as `Packed` instead of `T` writes those same bytes because + // it's the same size (thus zero offset), but with alignment 1 instead. + unsafe { write(dst, src) } } /// Performs a volatile read of the value from `src` without moving it. @@ -2809,3 +2821,6 @@ pub macro addr_of($place:expr) { pub macro addr_of_mut($place:expr) { &raw mut $place } + +#[repr(C, packed)] +struct Packed(T); diff --git a/tests/mir-opt/pre-codegen/unaligned.rs b/tests/mir-opt/pre-codegen/unaligned.rs index 70fbc373ca499..4aeb10c261e21 100644 --- a/tests/mir-opt/pre-codegen/unaligned.rs +++ b/tests/mir-opt/pre-codegen/unaligned.rs @@ -1,5 +1,5 @@ //@ compile-flags: -O -Zmir-opt-level=2 -//@ ignore-std-debug-assertions (there's one in `ptr::read` via `MaybeUninit::assume_init`) +//@ ignore-std-debug-assertions (there's one in `ptr::read`) #![crate_type = "lib"] @@ -24,12 +24,14 @@ pub unsafe fn unaligned_copy_generic(src: *const T, dst: *mut T) { // CHECK: debug src => _1; // CHECK: debug dst => _2; // CHECK: debug val => [[VAL:_.+]]; - // CHECK: copy _1 as *const u8 (PtrToPtr); - // CHECK: &raw mut - // CHECK: copy_nonoverlapping{{.+}}count = const ::SIZE - // CHECK: &raw const - // CHECK: copy _2 as *mut u8 (PtrToPtr); - // CHECK: copy_nonoverlapping{{.+}}count = const ::SIZE + // CHECK: [[SRC_P:_.+]] = copy _1 as *const {{.+}}::Packed (PtrToPtr); + // CHECK: [[PACKED1:_.+]] = copy (*[[SRC_P]]); + // CHECK: [[VAL]] = copy [[PACKED1]] as T (Transmute); + // CHECK: [[DST_P:_.+]] = copy _2 as *mut {{.+}}::Packed (PtrToPtr); + // CHECK: [[PACKED2:_.+]] = {{.+}}::Packed::(copy [[VAL]]); + // CHECK: (*[[DST_P]]) = copy [[PACKED2]]; + // CHECK-NOT: copy_nonoverlapping + // CHECK-NOT: drop unsafe { let val = std::ptr::read_unaligned(src); std::ptr::write_unaligned(dst, val); diff --git a/tests/mir-opt/pre-codegen/unaligned.unaligned_copy_generic.runtime-optimized.after.mir b/tests/mir-opt/pre-codegen/unaligned.unaligned_copy_generic.runtime-optimized.after.mir index ad474ba1edb73..4d0cfa6a4dfa3 100644 --- a/tests/mir-opt/pre-codegen/unaligned.unaligned_copy_generic.runtime-optimized.after.mir +++ b/tests/mir-opt/pre-codegen/unaligned.unaligned_copy_generic.runtime-optimized.after.mir @@ -4,96 +4,54 @@ fn unaligned_copy_generic(_1: *const T, _2: *mut T) -> () { debug src => _1; debug dst => _2; let mut _0: (); - let _9: T; - let mut _10: T; + let _5: T; scope 1 { - debug val => _9; - scope 14 (inlined #[track_caller] write_unaligned::) { - let mut _11: *const T; - let mut _12: *const u8; - let mut _13: *mut u8; - scope 15 (inlined std::mem::size_of::) { - } - scope 16 (inlined std::ptr::copy_nonoverlapping::) { - scope 17 (inlined core::ub_checks::check_language_ub) { - scope 18 (inlined core::ub_checks::check_language_ub::runtime) { + debug val => _5; + scope 8 (inlined #[track_caller] write_unaligned::) { + let _6: *mut std::ptr::Packed; + scope 9 { + let _7: std::ptr::Packed; + scope 10 { + scope 12 (inlined #[track_caller] std::ptr::write::>) { } } - scope 19 (inlined std::mem::size_of::) { - } - scope 20 (inlined std::mem::align_of::) { - } + } + scope 11 (inlined std::ptr::mut_ptr::::cast::>) { } } } scope 2 (inlined #[track_caller] read_unaligned::) { - let mut _3: std::mem::MaybeUninit; - let mut _4: *const u8; - let mut _6: *mut u8; - let mut _7: std::mem::MaybeUninit; + let _3: *const std::ptr::Packed; scope 3 { - scope 5 (inlined MaybeUninit::::as_mut_ptr) { - let mut _5: *mut std::mem::MaybeUninit; - } - scope 6 (inlined std::mem::size_of::) { - } - scope 7 (inlined std::ptr::copy_nonoverlapping::) { - scope 8 (inlined core::ub_checks::check_language_ub) { - scope 9 (inlined core::ub_checks::check_language_ub::runtime) { - } - } - scope 10 (inlined std::mem::size_of::) { - } - scope 11 (inlined std::mem::align_of::) { + let _4: std::ptr::Packed; + scope 4 { + scope 7 (inlined transmute_neo::, T>) { } } - scope 12 (inlined #[track_caller] MaybeUninit::::assume_init) { - let _8: (); - scope 13 (inlined transmute_neo::, T>) { - } + scope 6 (inlined #[track_caller] std::ptr::read::>) { } } - scope 4 (inlined MaybeUninit::::uninit) { + scope 5 (inlined std::ptr::const_ptr::::cast::>) { } } bb0: { - StorageLive(_9); StorageLive(_5); StorageLive(_3); - _3 = MaybeUninit:: { uninit: const () }; + _3 = copy _1 as *const std::ptr::Packed (PtrToPtr); StorageLive(_4); - _4 = copy _1 as *const u8 (PtrToPtr); - StorageLive(_6); - _5 = &raw mut _3; - _6 = copy _5 as *mut u8 (PtrToPtr); - copy_nonoverlapping(dst = copy _6, src = copy _4, count = const ::SIZE); - StorageDead(_6); + _4 = copy (*_3); + _5 = copy _4 as T (Transmute); StorageDead(_4); + StorageDead(_3); + StorageLive(_6); + _6 = copy _2 as *mut std::ptr::Packed (PtrToPtr); StorageLive(_7); - _7 = move _3; - _8 = assert_inhabited::() -> [return: bb1, unwind unreachable]; - } - - bb1: { - _9 = copy _7 as T (Transmute); + _7 = std::ptr::Packed::(copy _5); + (*_6) = copy _7; StorageDead(_7); - StorageDead(_3); + StorageDead(_6); StorageDead(_5); - StorageLive(_10); - _10 = move _9; - StorageLive(_11); - StorageLive(_12); - _11 = &raw const _10; - _12 = copy _11 as *const u8 (PtrToPtr); - StorageLive(_13); - _13 = copy _2 as *mut u8 (PtrToPtr); - copy_nonoverlapping(dst = copy _13, src = copy _12, count = const ::SIZE); - StorageDead(_13); - StorageDead(_12); - StorageDead(_11); - StorageDead(_10); - StorageDead(_9); return; } }