Skip to content

Commit a5ac44e

Browse files
committed
Add intrinsic for Option::Some(_) offset
1 parent 8efa635 commit a5ac44e

File tree

6 files changed

+41
-44
lines changed

6 files changed

+41
-44
lines changed

compiler/rustc_codegen_ssa/src/mir/intrinsic.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt};
1313
use rustc_span::{sym, Span};
1414
use rustc_target::abi::{
1515
call::{FnAbi, PassMode},
16-
WrappingRange,
16+
FieldsShape, Variants, WrappingRange,
1717
};
1818

1919
fn copy_intrinsic<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
@@ -104,6 +104,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
104104
bx.const_usize(bx.layout_of(tp_ty).align.abi.bytes())
105105
}
106106
}
107+
sym::option_some_offset => {
108+
let ty = substs.type_at(0);
109+
let layout = bx.layout_of(ty);
110+
let Variants::Multiple { variants, .. } = layout.layout.variants() else { bug!() };
111+
let Some(variant) = variants.iter().last() else { bug!() };
112+
let FieldsShape::Arbitrary { offsets, .. } = &variant.fields else { bug!() };
113+
bx.const_usize(offsets[0].bytes())
114+
}
107115
sym::vtable_size | sym::vtable_align => {
108116
let vtable = args[0].immediate();
109117
let idx = match name {

compiler/rustc_const_eval/src/interpret/intrinsics.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_middle::ty::layout::{LayoutOf as _, ValidityRequirement};
1515
use rustc_middle::ty::subst::SubstsRef;
1616
use rustc_middle::ty::{Ty, TyCtxt};
1717
use rustc_span::symbol::{sym, Symbol};
18-
use rustc_target::abi::{Abi, Align, Primitive, Size};
18+
use rustc_target::abi::{Abi, Align, FieldsShape, Primitive, Size, Variants};
1919

2020
use super::{
2121
util::ensure_monomorphic_enough, CheckInAllocMsg, ImmTy, InterpCx, Machine, OpTy, PlaceTy,
@@ -106,6 +106,13 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>(
106106
| ty::Tuple(_)
107107
| ty::Error(_) => ConstValue::from_target_usize(0u64, &tcx),
108108
},
109+
sym::option_some_offset => {
110+
let layout = tcx.layout_of(param_env.and(tp_ty)).map_err(|e| err_inval!(Layout(e)))?;
111+
let Variants::Multiple { variants, .. } = layout.layout.variants() else { bug!() };
112+
let Some(variant) = variants.iter().last() else { bug!() };
113+
let FieldsShape::Arbitrary { offsets, .. } = &variant.fields else { bug!() };
114+
ConstValue::from_target_usize(offsets[0].bytes(), &tcx)
115+
}
109116
other => bug!("`{}` is not a zero arg intrinsic", other),
110117
})
111118
}

compiler/rustc_hir_analysis/src/check/intrinsic.rs

+2
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: DefId) -> hir
110110
| sym::rustc_peek
111111
| sym::maxnumf64
112112
| sym::type_name
113+
| sym::option_some_offset
113114
| sym::forget
114115
| sym::black_box
115116
| sym::variant_count
@@ -214,6 +215,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
214215

215216
sym::type_name => (1, Vec::new(), tcx.mk_static_str()),
216217
sym::type_id => (1, Vec::new(), tcx.types.u64),
218+
sym::option_some_offset => (1, Vec::new(), tcx.types.usize),
217219
sym::offset | sym::arith_offset => (
218220
1,
219221
vec![

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,7 @@ symbols! {
10441044
optin_builtin_traits,
10451045
option,
10461046
option_env,
1047+
option_some_offset,
10471048
options,
10481049
or,
10491050
or_patterns,

library/core/src/intrinsics.rs

+10
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,16 @@ extern "rust-intrinsic" {
13031303
#[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")]
13041304
pub fn arith_offset<T>(dst: *const T, offset: isize) -> *const T;
13051305

1306+
#[cfg(not(bootstrap))]
1307+
/// The offset of the `Some(_)` value of an `Option<T>`. Used internally for
1308+
/// `Option::as_slice` and `Option::as_mut_slice`. This needs to be called with an
1309+
/// `Option<_>` type.
1310+
// FIXME: This should be replaced once we get a stable public method for getting
1311+
// the offset of enum variant's fields (e.g. an extension of `offset_of!` to enums)
1312+
#[rustc_const_stable(feature = "const_option_some_offset", since = "1.68.0")]
1313+
#[rustc_safe_intrinsic]
1314+
pub fn option_some_offset<T>() -> usize;
1315+
13061316
/// Masks out bits of the pointer according to a mask.
13071317
///
13081318
/// Note that, unlike most intrinsics, this is safe to call;

library/core/src/option.rs

+11-42
Original file line numberDiff line numberDiff line change
@@ -736,46 +736,15 @@ impl<T> Option<T> {
736736
}
737737

738738
/// This is a guess at how many bytes into the option the payload can be found.
739-
///
740-
/// For niche-optimized types it's correct because it's pigeon-holed to only
741-
/// one possible place. For other types, it's usually correct today, but
742-
/// tweaks to the layout algorithm (particularly expansions of
743-
/// `-Z randomize-layout`) might make it incorrect at any point.
744-
///
745-
/// It's guaranteed to be a multiple of alignment (so will always give a
746-
/// correctly-aligned location) and to be within the allocated object, so
747-
/// is valid to use with `offset` and to use for a zero-sized read.
748-
///
749-
/// FIXME: This is a horrible hack, but allows a nice optimization. It should
750-
/// be replaced with `offset_of!` once that works on enum variants.
751-
const SOME_BYTE_OFFSET_GUESS: isize = {
752-
let some_uninit = Some(mem::MaybeUninit::<T>::uninit());
753-
let payload_ref = some_uninit.as_ref().unwrap();
754-
// SAFETY: `as_ref` gives an address inside the existing `Option`,
755-
// so both pointers are derived from the same thing and the result
756-
// cannot overflow an `isize`.
757-
let offset = unsafe { <*const _>::byte_offset_from(payload_ref, &some_uninit) };
758-
759-
// The offset is into the object, so it's guaranteed to be non-negative.
760-
assert!(offset >= 0);
761-
762-
// The payload and the overall option are aligned,
763-
// so the offset will be a multiple of the alignment too.
764-
assert!((offset as usize) % mem::align_of::<T>() == 0);
765-
766-
let max_offset = mem::size_of::<Self>() - mem::size_of::<T>();
767-
if offset as usize <= max_offset {
768-
// There's enough space after this offset for a `T` to exist without
769-
// overflowing the bounds of the object, so let's try it.
770-
offset
771-
} else {
772-
// The offset guess is definitely wrong, so use the address
773-
// of the original option since we have it already.
774-
// This also correctly handles the case of layout-optimized enums
775-
// where `max_offset == 0` and thus this is the only possibility.
776-
0
777-
}
778-
};
739+
/// As this version will only be ever used to compile rustc and the performance
740+
/// penalty is negligible, use a minimal implementation here.
741+
#[cfg(bootstrap)]
742+
const SOME_BYTE_OFFSET_GUESS: usize = 0;
743+
744+
// FIXME: replace this with whatever stable method to get the offset of an enum
745+
// field may appear first.
746+
#[cfg(not(bootstrap))]
747+
const SOME_BYTE_OFFSET_GUESS: usize = crate::intrinsics::option_some_offset::<Option<T>>();
779748

780749
/// Returns a slice of the contained value, if any. If this is `None`, an
781750
/// empty slice is returned. This can be useful to have a single type of
@@ -820,7 +789,7 @@ impl<T> Option<T> {
820789
let self_ptr: *const Self = self;
821790
// SAFETY: `SOME_BYTE_OFFSET_GUESS` guarantees that its value is
822791
// such that this will be in-bounds of the object.
823-
unsafe { self_ptr.byte_offset(Self::SOME_BYTE_OFFSET_GUESS).cast() }
792+
unsafe { self_ptr.byte_add(Self::SOME_BYTE_OFFSET_GUESS).cast() }
824793
};
825794
let len = usize::from(self.is_some());
826795

@@ -886,7 +855,7 @@ impl<T> Option<T> {
886855
let self_ptr: *mut Self = self;
887856
// SAFETY: `SOME_BYTE_OFFSET_GUESS` guarantees that its value is
888857
// such that this will be in-bounds of the object.
889-
unsafe { self_ptr.byte_offset(Self::SOME_BYTE_OFFSET_GUESS).cast() }
858+
unsafe { self_ptr.byte_add(Self::SOME_BYTE_OFFSET_GUESS).cast() }
890859
};
891860
let len = usize::from(self.is_some());
892861

0 commit comments

Comments
 (0)