Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions compiler/rustc_abi/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,11 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
// If `-Z randomize-layout` was enabled for the type definition we can shuffle
// the field ordering to try and catch some code making assumptions about layouts
// we don't guarantee.
// In the future, we might do more than shuffle field order (e.g. introduce extra padding),
// but never for `repr(Rust)` structs with only zero-sized fields, single-variant
// `repr(Rust)` enums with only zero-sized fields, or zero-variant `repr(Rust)` enums,
// which must remain zero-sized as per T-lang decisions in
// https://github.com/rust-lang/reference/pull/2262 and https://github.com/rust-lang/reference/pull/2293
if repr.can_randomize_type_layout() && cfg!(feature = "randomize") {
#[cfg(feature = "randomize")]
{
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ bitflags! {
/// If true, the type's crate has opted into layout randomization.
/// Other flags can still inhibit reordering and thus randomization.
/// The seed stored in `ReprOptions.field_shuffle_seed`.
///
/// `repr(Rust)` structs with only zero-sized fields, single-variant `repr(Rust)` enums with only
/// zero-sized fields, and zero-variant `repr(Rust)` enums must remain zero-sized as per
/// T-lang decisions in https://github.com/rust-lang/reference/pull/2262 and https://github.com/rust-lang/reference/pull/2293
const RANDOMIZE_LAYOUT = 1 << 4;
/// If true, the type is always passed indirectly by non-Rustic ABIs.
/// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details.
Expand Down
97 changes: 60 additions & 37 deletions compiler/rustc_mir_transform/src/early_otherwise_branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ fn evaluate_candidate<'tcx>(
return None;
}

// We only handle:
// For now, we only handle:
// ```
// bb4: {
// _8 = discriminant((_3.1: Enum1));
Expand All @@ -262,41 +262,8 @@ fn evaluate_candidate<'tcx>(

// When thie BB has exactly one statement, this statement should be discriminant.
let need_hoist_discriminant = bbs[child].statements.len() == 1;
let otherwise_is_empty_unreachable = bbs[targets.otherwise()].is_empty_unreachable();
let child_place = if need_hoist_discriminant {
if !bbs[targets.otherwise()].is_empty_unreachable() {
// Someone could write code like this:
// ```rust
// let Q = val;
// if discriminant(P) == otherwise {
// let ptr = &mut Q as *mut _ as *mut u8;
// // It may be difficult for us to effectively determine whether values are valid.
// // Invalid values can come from all sorts of corners.
// unsafe { *ptr = 10; }
// }
//
// match P {
// A => match Q {
// A => {
// // code
// }
// _ => {
// // don't use Q
// }
// }
// _ => {
// // don't use Q
// }
// };
// ```
//
// Hoisting the `discriminant(Q)` out of the `A` arm causes us to compute the discriminant of an
// invalid value, which is UB.
// In order to fix this, **we would either need to show that the discriminant computation of
// `place` is computed in all branches**.
// FIXME(#95162) For the moment, we adopt a conservative approach and
// consider only the `otherwise` branch has no statements and an unreachable terminator.
return None;
}
// Handle:
// ```
// bb4: {
Expand Down Expand Up @@ -325,8 +292,7 @@ fn evaluate_candidate<'tcx>(
};
*child_place
};
let destination = if need_hoist_discriminant || bbs[targets.otherwise()].is_empty_unreachable()
{
let destination = if otherwise_is_empty_unreachable {
child_targets.otherwise()
} else {
targets.otherwise()
Expand All @@ -340,6 +306,7 @@ fn evaluate_candidate<'tcx>(
child_place,
destination,
need_hoist_discriminant,
otherwise_is_empty_unreachable,
) {
return None;
}
Expand All @@ -359,11 +326,67 @@ fn verify_candidate_branch<'tcx>(
place: Place<'tcx>,
destination: BasicBlock,
need_hoist_discriminant: bool,
otherwise_is_empty_unreachable: bool,
) -> bool {
// In order for the optimization to be correct, the terminator must be a `SwitchInt`.
let TerminatorKind::SwitchInt { discr: switch_op, targets } = &branch.terminator().kind else {
return false;
};
if !otherwise_is_empty_unreachable {
// Someone could write code like this:
// ```rust
// let Q = val;
// if discriminant(P) == otherwise {
// let ptr = &mut Q as *mut _ as *mut u8;
// // It may be difficult for us to effectively determine whether values are valid.
// // Invalid values can come from all sorts of corners.
// unsafe { *ptr = 10; }
// }
//
// match P {
// A => match Q {
// A => {
// // code
// }
// _ => {
// // don't use Q
// }
// }
// _ => {
// // don't use Q
// }
// };
// ```
//
// Hoisting the `discriminant(Q)` out of the `A` arm causes us to compute the discriminant of an
// invalid value, which is UB.
// In order to fix this, **we would either need to show that the discriminant computation of
// `place` is computed in all branches**.
// For <https://github.com/rust-lang/rust/issues/95162>, we adopt a conservative approach and
// consider only the `otherwise` branch has no statements and an unreachable terminator.
if need_hoist_discriminant {
return false;
}
// For <https://github.com/rust-lang/rust/issues/159591>:
// ```
// bb0: {
// switchInt(copy _1) -> [1: bb1, 2: bb2, otherwise: bb5];
// }
// bb1: {
// switchInt(copy (*_2)) -> [1: bb3, otherwise: bb5];
// }
// bb2: {
// switchInt(copy (*_2)) -> [2: bb4, otherwise: bb5];
// }
// ```
// We cannot hoist the dereference of `_2` to `bb0`,
// because execution can reach `bb5` without dereferencing `_2`.
if let Some(place) = switch_op.place()
&& !place.is_stable_offset()
{
return false;
}
}
if need_hoist_discriminant {
// If we need hoist discriminant, the branch must have exactly one statement.
let [statement] = branch.statements.as_slice() else {
Expand Down
9 changes: 6 additions & 3 deletions src/doc/rustc/src/symbol-mangling/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ The [`#[export_name]`attribute][reference-export_name] can be used to specify th
Items listed in an [`extern` block][reference-extern-block] use the identifier of the item without mangling to refer to the item.
The [`#[link_name]` attribute][reference-link_name] can be used to change that name.

<!--
FIXME: This is incomplete for wasm, per https://github.com/rust-lang/rust/blob/d4c364347ce65cf083d4419195b8232440928d4d/compiler/rustc_symbol_mangling/src/lib.rs#L191-L210
-->
### WebAssembly import modules

On WebAssembly targets, foreign items in `extern` blocks can use the same import name without
conflicting when they use different [`#[link(wasm_import_module = "...")]`][reference-link-attribute]
values.

[reference-no_mangle]: ../../reference/abi.html#the-no_mangle-attribute
[reference-export_name]: ../../reference/abi.html#the-export_name-attribute
[reference-link_name]: ../../reference/items/external-blocks.html#the-link_name-attribute
[reference-link-attribute]: ../../reference/items/external-blocks.html#the-link-attribute
[reference-extern-block]: ../../reference/items/external-blocks.html

## Decoding
Expand Down
4 changes: 3 additions & 1 deletion tests/assembly-llvm/asm/aarch64-types.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//@ add-minicore
//@ revisions: aarch64 arm64ec
//@ revisions: aarch64 aarch64_be arm64ec
//@ assembly-output: emit-asm
//@ [aarch64] compile-flags: --target aarch64-unknown-linux-gnu
//@ [aarch64] needs-llvm-components: aarch64
//@ [aarch64_be] compile-flags: --target aarch64_be-unknown-linux-gnu
//@ [aarch64_be] needs-llvm-components: aarch64
//@ [arm64ec] compile-flags: --target arm64ec-pc-windows-msvc
//@ [arm64ec] needs-llvm-components: aarch64
//@ compile-flags: -Zmerge-functions=disabled
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
- // MIR for `dont_hoist_deref` before EarlyOtherwiseBranch
+ // MIR for `dont_hoist_deref` after EarlyOtherwiseBranch

fn dont_hoist_deref(_1: u64, _2: *const u64) -> u64 {
let mut _0: u64;

bb0: {
switchInt(copy _1) -> [1: bb1, 2: bb2, otherwise: bb5];
}

bb1: {
switchInt(copy (*_2)) -> [1: bb3, otherwise: bb5];
}

bb2: {
switchInt(copy (*_2)) -> [2: bb4, otherwise: bb5];
}

bb3: {
_0 = const 100_u64;
goto -> bb6;
}

bb4: {
_0 = const 200_u64;
goto -> bb6;
}

bb5: {
_0 = const 999_u64;
goto -> bb6;
}

bb6: {
return;
}
}

51 changes: 51 additions & 0 deletions tests/mir-opt/early_otherwise_branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,56 @@ fn target_self(val: i32) {
}
}

// EMIT_MIR early_otherwise_branch.dont_hoist_deref.EarlyOtherwiseBranch.diff
#[custom_mir(dialect = "runtime")]
fn dont_hoist_deref(q: u64, p: *const u64) -> u64 {
// The dereference of `p` cannot be hoisted because the `otherwise` branch
// can be taken without dereferencing `p`.
// Hoisting the dereference could therefore cause UB when `p` is null.
// Hoisting a dereference also requires proving that the dereference is safe to reorder.
// CHECK-LABEL: fn dont_hoist_deref(
// CHECK: bb0: {
// CHECK-NEXT: switchInt(copy _1)
// CHECK: switchInt(copy (*_2))
// CHECK: switchInt(copy (*_2))
mir! {
{
match q {
1 => bb1,
2 => bb2,
_ => bb5,
}
}
bb1 = {
match *p {
1 => bb3,
_ => bb5,
}
}
bb2 = {
match *p {
2 => bb4,
_ => bb5,
}
}
bb3 = {
RET = 100;
Goto(bb6)
}
bb4 = {
RET = 200;
Goto(bb6)
}
bb5 = {
RET = 999;
Goto(bb6)
}
bb6 = {
Return()
}
}
}

fn main() {
opt1(None, Some(0));
opt2(None, Some(0));
Expand All @@ -164,4 +214,5 @@ fn main() {
opt5(0, 0);
opt5_failed(0, 0);
target_self(1);
dont_hoist_deref(3, std::ptr::null());
}
46 changes: 46 additions & 0 deletions tests/ui/layout/randomize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,52 @@ const _: () = {
assert!(std::mem::offset_of!(Result::<&usize, ()>, Ok.0) == 0);
};

// these types only have their size checked, they're never constructed.
// these repr(Rust) types must remain zero-sized.
#[allow(dead_code)]
pub struct UnitStruct;
#[allow(dead_code)]
pub struct EmptyTupleStruct();
#[allow(dead_code)]
pub struct EmptyStruct {}
#[allow(dead_code)]
pub struct ZstFieldsTupleStruct((), [u64; 0], [u8; 0], [(); 42]);
#[allow(dead_code)]
pub struct ZstFieldsStruct {
a: (),
b: [u64; 0],
c: [u8; 0],
d: [(); 42],
}
#[allow(dead_code)]
pub enum EmptyEnum {}
#[allow(dead_code)]
pub enum SingleUnitVariantEnum { A }
#[allow(dead_code)]
pub enum SingleZstFieldTupleVariantEnum { A((), [u64; 0], [u8; 0], [(); 42]) }
#[allow(dead_code)]
pub enum SingleZstFieldVariantEnum {
A {
a: (),
b: [u64; 0],
c: [u8; 0],
d: [(); 42],
}
}

// all these types must remain zero-sized.
const _: () = {
assert!(size_of::<UnitStruct>() == 0);
assert!(size_of::<EmptyTupleStruct>() == 0);
assert!(size_of::<EmptyStruct>() == 0);
assert!(size_of::<ZstFieldsTupleStruct>() == 0);
assert!(size_of::<ZstFieldsStruct>() == 0);
assert!(size_of::<EmptyEnum>() == 0);
assert!(size_of::<SingleUnitVariantEnum>() == 0);
assert!(size_of::<SingleZstFieldTupleVariantEnum>() == 0);
assert!(size_of::<SingleZstFieldVariantEnum>() == 0);
};

#[allow(dead_code)]
struct Unsizable<T: ?Sized>(usize, T);

Expand Down
36 changes: 36 additions & 0 deletions tests/ui/mir/hoist_deref_early_else.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Regression test for issue <https://github.com/rust-lang/rust/issues/159591>.
//! The null pointer `p` should never be dereferenced.
//@ run-pass
//@ revisions: noopt opt
//@ check-run-results
//@[noopt] compile-flags: -C opt-level=0
//@[opt] compile-flags: -C opt-level=3

use std::hint::black_box;

#[inline(never)]
fn foo(q: u64, p: *const u64) -> u64 {
unsafe {
'a: {
match q {
1 => match *p {
1 => break 'a 100,
_ => {}
},
2 => match *p {
2 => break 'a 200,
_ => {}
},
_ => {}
}
999
}
}
}

fn main() {
let q: u64 = black_box(3);
let p: *const u64 = black_box(std::ptr::null());
let r = foo(q, p);
assert_eq!(999, black_box(r));
}
Loading