Skip to content

Commit 06e5097

Browse files
committed
Auto merge of rust-lang#115870 - RalfJung:const-value-slice, r=<try>
adjust constValue::Slice to work for arbitrary slice types valtrees have already been assuming that this works; this PR makes it a reality. Also further restrict `ConstValue::Slice` to what it is actually used for; this even shrinks `ConstValue` from 32 to 24 bytes which is a nice win. :) The alternative to this approach is to make `ConstValue::Slice` work really only for `&str`/`&[u8]` literals, and never return it in `op_to_const`. That would make `op_to_const` very clean. We could then even remove the `meta` field; the length would always be `data.inner().len()`. We could *almost* just use a `Symbol` instead of a `ConstAllocation`, but we have to support byte strings and there doesn't seem to be an interned representation of them (or rather, `ConstAllocation` *is* their interned representation). In this world, valtrees of slice reference types would then become noticeably more expensive to turn into a `ConstValue` -- but does that matter? Specifically for `&str`/`&[u8]` we could still use the optimized representation if we wanted. If byte strings were already interned somewhere I'd gravitate towards the alternative, but the way things stand, we need a `ConstAllocation` case anyway to support byte strings, and then we might as well support arbitrary slices. (Or we say that byte strings don't get an optimized representation at all. Such a performance cliff between `str` and byte strings is probably unexpected, though due to the lack of interning for byte strings I think there might already be a performance cliff there.)
2 parents e7f9f48 + e989ec5 commit 06e5097

File tree

11 files changed

+76
-63
lines changed

11 files changed

+76
-63
lines changed

compiler/rustc_codegen_cranelift/src/constant.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -205,15 +205,11 @@ pub(crate) fn codegen_const_value<'tcx>(
205205
.offset_i64(fx, i64::try_from(offset.bytes()).unwrap()),
206206
layout,
207207
),
208-
ConstValue::Slice { data, start, end } => {
208+
ConstValue::Slice { data, meta } => {
209209
let alloc_id = fx.tcx.reserve_and_set_memory_alloc(data);
210-
let ptr = pointer_for_allocation(fx, alloc_id)
211-
.offset_i64(fx, i64::try_from(start).unwrap())
212-
.get_addr(fx);
213-
let len = fx
214-
.bcx
215-
.ins()
216-
.iconst(fx.pointer_type, i64::try_from(end.checked_sub(start).unwrap()).unwrap());
210+
let ptr = pointer_for_allocation(fx, alloc_id).get_addr(fx);
211+
// FIXME: the `try_from` here can actually fail, e.g. for very long ZST slices.
212+
let len = fx.bcx.ins().iconst(fx.pointer_type, i64::try_from(meta).unwrap());
217213
CValue::by_val_pair(ptr, len, layout)
218214
}
219215
}

compiler/rustc_codegen_ssa/src/mir/operand.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -100,23 +100,20 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
100100
OperandValue::Immediate(llval)
101101
}
102102
ConstValue::ZeroSized => return OperandRef::zero_sized(layout),
103-
ConstValue::Slice { data, start, end } => {
103+
ConstValue::Slice { data, meta } => {
104104
let Abi::ScalarPair(a_scalar, _) = layout.abi else {
105105
bug!("from_const: invalid ScalarPair layout: {:#?}", layout);
106106
};
107107
let a = Scalar::from_pointer(
108-
Pointer::new(
109-
bx.tcx().reserve_and_set_memory_alloc(data),
110-
Size::from_bytes(start),
111-
),
108+
Pointer::new(bx.tcx().reserve_and_set_memory_alloc(data), Size::ZERO),
112109
&bx.tcx(),
113110
);
114111
let a_llval = bx.scalar_to_backend(
115112
a,
116113
a_scalar,
117114
bx.scalar_pair_element_backend_type(layout, 0, true),
118115
);
119-
let b_llval = bx.const_usize((end - start) as u64);
116+
let b_llval = bx.const_usize(meta);
120117
OperandValue::Pair(a_llval, b_llval)
121118
}
122119
ConstValue::Indirect { alloc_id, offset } => {

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

+18-9
Original file line numberDiff line numberDiff line change
@@ -152,19 +152,28 @@ pub(super) fn op_to_const<'tcx>(
152152
Immediate::Scalar(x) => ConstValue::Scalar(x),
153153
Immediate::ScalarPair(a, b) => {
154154
debug!("ScalarPair(a: {:?}, b: {:?})", a, b);
155-
// FIXME: assert that this has an appropriate type.
156-
// Currently we actually get here for non-[u8] slices during valtree construction!
157-
let msg = "`op_to_const` on an immediate scalar pair must only be used on slice references to actually allocated memory";
155+
// This codepath solely exists for `valtree_to_const_value` to not need to generate
156+
// a `ConstValue::Indirect` for wide references, so it is tightly restricted to just
157+
// that case.
158+
let pointee_ty = imm.layout.ty.builtin_deref(false).unwrap().ty; // `false` = no raw ptrs
159+
debug_assert!(
160+
matches!(
161+
ecx.tcx.struct_tail_without_normalization(pointee_ty).kind(),
162+
ty::Str | ty::Slice(..),
163+
),
164+
"`ConstValue::Slice` is for slice-tailed types only, but got {}",
165+
imm.layout.ty,
166+
);
167+
let msg = "`op_to_const` on an immediate scalar pair must only be used on slice references to the beginning of an actual allocation";
158168
// We know `offset` is relative to the allocation, so we can use `into_parts`.
159-
// We use `ConstValue::Slice` so that we don't have to generate an allocation for
160-
// `ConstValue::Indirect` here.
161169
let (alloc_id, offset) = a.to_pointer(ecx).expect(msg).into_parts();
162170
let alloc_id = alloc_id.expect(msg);
163171
let data = ecx.tcx.global_alloc(alloc_id).unwrap_memory();
164-
let start = offset.bytes_usize();
165-
let len = b.to_target_usize(ecx).expect(msg);
166-
let len: usize = len.try_into().unwrap();
167-
ConstValue::Slice { data, start, end: start + len }
172+
if offset.bytes() != 0 {
173+
panic!("{}", msg);
174+
}
175+
let meta = b.to_target_usize(ecx).expect(msg);
176+
ConstValue::Slice { data, meta }
168177
}
169178
Immediate::Uninit => bug!("`Uninit` is not a valid value for {}", op.layout.ty),
170179
},

compiler/rustc_const_eval/src/interpret/cast.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
351351

352352
match (&src_pointee_ty.kind(), &dest_pointee_ty.kind()) {
353353
(&ty::Array(_, length), &ty::Slice(_)) => {
354-
let ptr = self.read_scalar(src)?;
354+
let ptr = self.read_pointer(src)?;
355355
// u64 cast is from usize to u64, which is always good
356356
let val = Immediate::new_slice(
357357
ptr,
@@ -367,6 +367,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
367367
return self.write_immediate(*val, dest);
368368
}
369369
let (old_data, old_vptr) = val.to_scalar_pair();
370+
let old_data = old_data.to_pointer(self)?;
370371
let old_vptr = old_vptr.to_pointer(self)?;
371372
let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?;
372373
if old_trait != data_a.principal() {
@@ -378,7 +379,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
378379
(_, &ty::Dynamic(data, _, ty::Dyn)) => {
379380
// Initial cast from sized to dyn trait
380381
let vtable = self.get_vtable_ptr(src_pointee_ty, data.principal())?;
381-
let ptr = self.read_scalar(src)?;
382+
let ptr = self.read_pointer(src)?;
382383
let val = Immediate::new_dyn_trait(ptr, vtable, &*self.tcx);
383384
self.write_immediate(val, dest)
384385
}

compiler/rustc_const_eval/src/interpret/intrinsics.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>(
6464
sym::type_name => {
6565
ensure_monomorphic_enough(tcx, tp_ty)?;
6666
let alloc = alloc_type_name(tcx, tp_ty);
67-
ConstValue::Slice { data: alloc, start: 0, end: alloc.inner().len() }
67+
ConstValue::Slice { data: alloc, meta: alloc.inner().size().bytes() }
6868
}
6969
sym::needs_drop => {
7070
ensure_monomorphic_enough(tcx, tp_ty)?;

compiler/rustc_const_eval/src/interpret/operand.rs

+18-15
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,30 @@ impl<Prov: Provenance> From<Scalar<Prov>> for Immediate<Prov> {
4545
}
4646

4747
impl<Prov: Provenance> Immediate<Prov> {
48-
pub fn from_pointer(p: Pointer<Prov>, cx: &impl HasDataLayout) -> Self {
49-
Immediate::Scalar(Scalar::from_pointer(p, cx))
48+
pub fn from_pointer(ptr: Pointer<Prov>, cx: &impl HasDataLayout) -> Self {
49+
Immediate::Scalar(Scalar::from_pointer(ptr, cx))
5050
}
5151

52-
pub fn from_maybe_pointer(p: Pointer<Option<Prov>>, cx: &impl HasDataLayout) -> Self {
53-
Immediate::Scalar(Scalar::from_maybe_pointer(p, cx))
52+
pub fn from_maybe_pointer(ptr: Pointer<Option<Prov>>, cx: &impl HasDataLayout) -> Self {
53+
Immediate::Scalar(Scalar::from_maybe_pointer(ptr, cx))
5454
}
5555

56-
pub fn new_slice(val: Scalar<Prov>, len: u64, cx: &impl HasDataLayout) -> Self {
57-
Immediate::ScalarPair(val, Scalar::from_target_usize(len, cx))
56+
pub fn new_slice(ptr: Pointer<Option<Prov>>, len: u64, cx: &impl HasDataLayout) -> Self {
57+
Immediate::ScalarPair(
58+
Scalar::from_maybe_pointer(ptr, cx),
59+
Scalar::from_target_usize(len, cx),
60+
)
5861
}
5962

6063
pub fn new_dyn_trait(
61-
val: Scalar<Prov>,
64+
val: Pointer<Option<Prov>>,
6265
vtable: Pointer<Option<Prov>>,
6366
cx: &impl HasDataLayout,
6467
) -> Self {
65-
Immediate::ScalarPair(val, Scalar::from_maybe_pointer(vtable, cx))
68+
Immediate::ScalarPair(
69+
Scalar::from_maybe_pointer(val, cx),
70+
Scalar::from_maybe_pointer(vtable, cx),
71+
)
6672
}
6773

6874
#[inline]
@@ -764,16 +770,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
764770
}
765771
ConstValue::Scalar(x) => Operand::Immediate(adjust_scalar(x)?.into()),
766772
ConstValue::ZeroSized => Operand::Immediate(Immediate::Uninit),
767-
ConstValue::Slice { data, start, end } => {
773+
ConstValue::Slice { data, meta } => {
768774
// We rely on mutability being set correctly in `data` to prevent writes
769775
// where none should happen.
770-
let ptr = Pointer::new(
771-
self.tcx.reserve_and_set_memory_alloc(data),
772-
Size::from_bytes(start), // offset: `start`
773-
);
776+
let ptr = Pointer::new(self.tcx.reserve_and_set_memory_alloc(data), Size::ZERO);
774777
Operand::Immediate(Immediate::new_slice(
775-
Scalar::from_pointer(self.global_base_pointer(ptr)?, &*self.tcx),
776-
u64::try_from(end.checked_sub(start).unwrap()).unwrap(), // len: `end - start`
778+
self.global_base_pointer(ptr)?.into(),
779+
meta,
777780
self,
778781
))
779782
}

compiler/rustc_middle/src/mir/interpret/value.rs

+18-9
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,20 @@ pub enum ConstValue<'tcx> {
4141
/// Only for ZSTs.
4242
ZeroSized,
4343

44-
/// Used for `&[u8]` and `&str`.
44+
/// Used for references to unsized types with slice tail.
4545
///
46-
/// This is worth an optimized representation since Rust has literals of these types.
47-
/// Not having to indirect those through an `AllocId` (or two, if we used `Indirect`) has shown
48-
/// measurable performance improvements on stress tests.
49-
Slice { data: ConstAllocation<'tcx>, start: usize, end: usize },
46+
/// This is worth an optimized representation since Rust has literals of type `&str` and
47+
/// `&[u8]`. Not having to indirect those through an `AllocId` (or two, if we used `Indirect`)
48+
/// has shown measurable performance improvements on stress tests. We then reuse this
49+
/// optimization for slice-tail types more generally during valtree-to-constval conversion.
50+
Slice {
51+
/// The allocation storing the slice contents.
52+
/// This always points to the beginning of the allocation.
53+
data: ConstAllocation<'tcx>,
54+
/// The metadata field of the reference.
55+
/// This is a "target usize", so we use `u64` as in the interpreter.
56+
meta: u64,
57+
},
5058

5159
/// A value not representable by the other variants; needs to be stored in-memory.
5260
///
@@ -65,7 +73,7 @@ pub enum ConstValue<'tcx> {
6573
}
6674

6775
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
68-
static_assert_size!(ConstValue<'_>, 32);
76+
static_assert_size!(ConstValue<'_>, 24);
6977

7078
impl<'tcx> ConstValue<'tcx> {
7179
#[inline]
@@ -124,7 +132,7 @@ impl<'tcx> ConstValue<'tcx> {
124132
ConstValue::Scalar(_) | ConstValue::ZeroSized => {
125133
bug!("`try_get_slice_bytes` on non-slice constant")
126134
}
127-
&ConstValue::Slice { data, start, end } => (data, start, end),
135+
&ConstValue::Slice { data, meta } => (data, 0, meta),
128136
&ConstValue::Indirect { alloc_id, offset } => {
129137
// The reference itself is stored behind an indirection.
130138
// Load the reference, and then load the actual slice contents.
@@ -151,18 +159,19 @@ impl<'tcx> ConstValue<'tcx> {
151159
)
152160
.ok()?;
153161
let len = len.to_target_usize(&tcx).ok()?;
154-
let len: usize = len.try_into().ok()?;
155162
if len == 0 {
156163
return Some(&[]);
157164
}
158165
// Non-empty slice, must have memory. We know this is a relative pointer.
159166
let (inner_alloc_id, offset) = ptr.into_parts();
160167
let data = tcx.global_alloc(inner_alloc_id?).unwrap_memory();
161-
(data, offset.bytes_usize(), offset.bytes_usize() + len)
168+
(data, offset.bytes(), offset.bytes() + len)
162169
}
163170
};
164171

165172
// This is for diagnostics only, so we are okay to use `inspect_with_uninit_and_ptr_outside_interpreter`.
173+
let start = start.try_into().unwrap();
174+
let end = end.try_into().unwrap();
166175
Some(data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end))
167176
}
168177
}

compiler/rustc_mir_build/src/build/expr/as_constant.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,14 @@ fn lit_to_mir_constant<'tcx>(
133133
let s = s.as_str();
134134
let allocation = Allocation::from_bytes_byte_aligned_immutable(s.as_bytes());
135135
let allocation = tcx.mk_const_alloc(allocation);
136-
ConstValue::Slice { data: allocation, start: 0, end: s.len() }
136+
ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() }
137137
}
138138
(ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _))
139139
if matches!(inner_ty.kind(), ty::Slice(_)) =>
140140
{
141141
let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8]);
142142
let allocation = tcx.mk_const_alloc(allocation);
143-
ConstValue::Slice { data: allocation, start: 0, end: data.len() }
143+
ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() }
144144
}
145145
(ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _)) if inner_ty.is_array() => {
146146
let id = tcx.allocate_bytes(data);
@@ -150,7 +150,7 @@ fn lit_to_mir_constant<'tcx>(
150150
{
151151
let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8]);
152152
let allocation = tcx.mk_const_alloc(allocation);
153-
ConstValue::Slice { data: allocation, start: 0, end: data.len() }
153+
ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() }
154154
}
155155
(ast::LitKind::Byte(n), ty::Uint(ty::UintTy::U8)) => {
156156
ConstValue::Scalar(Scalar::from_uint(*n, Size::from_bytes(1)))

compiler/rustc_monomorphize/src/collector.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1448,7 +1448,7 @@ fn collect_const_value<'tcx>(
14481448
match value {
14491449
ConstValue::Scalar(Scalar::Ptr(ptr, _size)) => collect_alloc(tcx, ptr.provenance, output),
14501450
ConstValue::Indirect { alloc_id, .. } => collect_alloc(tcx, alloc_id, output),
1451-
ConstValue::Slice { data, start: _, end: _ } => {
1451+
ConstValue::Slice { data, meta: _ } => {
14521452
for &id in data.inner().provenance().ptrs().values() {
14531453
collect_alloc(tcx, id, output);
14541454
}

compiler/rustc_smir/src/rustc_smir/alloc.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,12 @@ pub fn new_allocation<'tcx>(
4444
tables.tcx.layout_of(rustc_middle::ty::ParamEnv::empty().and(ty)).unwrap().align;
4545
new_empty_allocation(align.abi)
4646
}
47-
ConstValue::Slice { data, start, end } => {
47+
ConstValue::Slice { data, meta } => {
4848
let alloc_id = tables.tcx.reserve_and_set_memory_alloc(data);
49-
let ptr = Pointer::new(alloc_id, rustc_target::abi::Size::from_bytes(start));
49+
let ptr = Pointer::new(alloc_id, rustc_target::abi::Size::ZERO);
5050
let scalar_ptr = rustc_middle::mir::interpret::Scalar::from_pointer(ptr, &tables.tcx);
51-
let scalar_len = rustc_middle::mir::interpret::Scalar::from_target_usize(
52-
(end - start) as u64,
53-
&tables.tcx,
54-
);
51+
let scalar_meta =
52+
rustc_middle::mir::interpret::Scalar::from_target_usize(meta, &tables.tcx);
5553
let layout =
5654
tables.tcx.layout_of(rustc_middle::ty::ParamEnv::reveal_all().and(ty)).unwrap();
5755
let mut allocation =
@@ -66,8 +64,8 @@ pub fn new_allocation<'tcx>(
6664
allocation
6765
.write_scalar(
6866
&tables.tcx,
69-
alloc_range(tables.tcx.data_layout.pointer_size, scalar_len.size()),
70-
scalar_len,
67+
alloc_range(tables.tcx.data_layout.pointer_size, scalar_meta.size()),
68+
scalar_meta,
7169
)
7270
.unwrap();
7371
allocation.stable(tables)

src/tools/miri/src/shims/backtrace.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
8989
}
9090

9191
this.write_immediate(
92-
Immediate::new_slice(Scalar::from_maybe_pointer(alloc.ptr(), this), len, this),
92+
Immediate::new_slice(alloc.ptr(), len, this),
9393
dest,
9494
)?;
9595
}

0 commit comments

Comments
 (0)