@@ -92,6 +92,136 @@ pub(crate) enum MetadataCastError {
92
92
Size ,
93
93
}
94
94
95
+ /// Parameters necessary to perform an infallible reference cast on slice DSTs.
96
+ ///
97
+ /// # Safety
98
+ ///
99
+ /// Given `Src: KnownLayout` and `Dst: KnownLayout`, it is only possible to
100
+ /// produce `CastParams` via
101
+ /// `Src::LAYOUT.try_compute_cast_params(&Dst::LAYOUT)`, and that method will
102
+ /// only return `Some` if it is possible to perform an infallible reference cast
103
+ /// from `&Src` to `&Dst`.
104
+ pub ( crate ) struct CastParams {
105
+ /// Given `sl: TrailingSliceLayout` (source layout) and `dl:
106
+ /// TrailingSliceLayout` (destination layout), this is computed as:
107
+ ///
108
+ /// `(sl.offset - dl.offset) / dl.elem_size`
109
+ ///
110
+ /// INVARIANT: `CastParams` can only be constructed if `sl.offset -
111
+ /// dl.offset` is an integer multiple of `dl.elem_size`.
112
+ offset_delta_elems : usize ,
113
+ /// `sl.elem_size / dl.elem_size`
114
+ ///
115
+ /// INVARIANT: `CastParams` can only be constructed if `sl.elem_size` is an
116
+ /// integer multiple of `dl.elem_size`, and if `dl.elem_size > 0`.
117
+ elem_multiple : usize ,
118
+ }
119
+
120
+ impl CastParams {
121
+ /// Given the metadata from `src: &Src`, computes the metadata required in
122
+ /// order for `dst: &Dst` with the same address to reference the same number
123
+ /// of bytes as `src`.
124
+ ///
125
+ /// # Safety
126
+ ///
127
+ /// If `Src: KnownLayout` and `Dst: KnownLayout`, and if `self` was computed
128
+ /// as `Src::LAYOUT.try_compute_cast_params(&Dst::LAYOUT)`, then the
129
+ /// following is guaranteed:
130
+ ///
131
+ /// Let `src_meta` be the pointer metadata from a `src: &Src`, and compute
132
+ /// `let dst_meta = self.compute_cast(src_meta)`. If `dst: &Dst` is
133
+ /// constructed using the address of `src` and using `dst_meta` as its
134
+ /// pointer metadata, then `dst` will address the same bytes as `src`.
135
+ ///
136
+ /// TODO: Mention that post-padding may not be preserved.
137
+ ///
138
+ /// # Panics
139
+ ///
140
+ /// If the safety preconditions do not hold, then `compute_cast` may panic
141
+ /// due to arithmetic overflow. Note that, as `compute_cast` is a safe
142
+ /// function, failing to uphold the safety preconditions cannot cause
143
+ /// immediate undefined behavior.
144
+ pub ( crate ) const fn compute_cast ( self , src_meta : usize ) -> usize {
145
+ // PANICS: This function is permitted to panic if its safety
146
+ // preconditions are not upheld.
147
+ //
148
+ // SAFETY/PANICS: If `Src: KnownLayout`, `Dst: KnownLayout`, and `self`
149
+ // was computed as `Src::LAYOUT.try_compute_cast_params(&Dst::LAYOUT)`,
150
+ // then `self` is a witness to the infallibility of casts from `&Src` to
151
+ // `&Dst`, and the internal invariants hold with respect to `Src` and
152
+ // `Dst`. Given `sl: Src::LAYOUT` and `dl: Dst::LAYOUT`, these
153
+ // invariants guarantee that:
154
+ //
155
+ // (1) `self.offset_delta_elems = (sl.offset - dl.offset) /
156
+ // dl.elem_size`
157
+ //
158
+ // (2) `self.elem_multiple = sl.elem_size / dl.elem_size` where
159
+ // `sl.elem_size` is an integer multiple of `dl.elem_size`
160
+ //
161
+ // If the safety preconditions are upheld, then the caller has promised
162
+ // that `src_meta` is the pointer metadata from some `src: &Src`. By
163
+ // invariant on `&Src`, `src` cannot address more than `isize::MAX`
164
+ // bytes [1]. Thus:
165
+ //
166
+ // (3) `sl.offset + (sl.elem_size * src_meta) = src_len <= isize::MAX`
167
+ //
168
+ // (4) `self.offset_delta_elems * dl.elem_size = sl.offset - dl.offset`
169
+ // (rearranging (1))
170
+ //
171
+ // (5) `sl.offset = (self.offset_delta_elems * dl.elem_size) +
172
+ // dl.offset`
173
+ //
174
+ // (6) `(self.offset_delta_elems * dl.elem_size) + dl.offset +
175
+ // (sl.elem_size * src_meta) = src_len <= isize::MAX` (substituting
176
+ // (5) into (3))
177
+ //
178
+ // (7) `sl.elem_size = self.elem_multiple * dl.elem_size` (rearranging
179
+ // (2))
180
+ //
181
+ // (8) `(self.offset_delta_elems * dl.elem_size) + dl.offset +
182
+ // (self.elem_multiple * dl.elem_size * src_meta) = src_len <=
183
+ // isize::MAX` (substituting (7) into (6))
184
+ //
185
+ // (9) `(self.offset_delta_elems * dl.elem_size) + (self.elem_multiple
186
+ // * dl.elem_size * src_meta) = src_len - dl.offset <= isize::MAX -
187
+ // dl.offset`
188
+ //
189
+ // (10) `self.offset_delta_elems + (self.elem_multiple * src_meta) =
190
+ // (src_len - dl.offset) / dl.elem_size <= (isize::MAX - dl.offset)
191
+ // / dl.elem_size`
192
+ //
193
+ // The left-hand side of (10) is the expression we compute and return
194
+ // from this method. Since `dl.offset >= 0` and `dl.elem_size > 1`, the
195
+ // right-hand side is not greater than `usize::MAX`, and thus this
196
+ // computation will not overflow, and thus it will not panic.
197
+ //
198
+ // We further need to prove that, if the caller takes this resulting
199
+ // value as `dst_meta` and uses it to synthesize a `dst &Dst`, this will
200
+ // be address the same number of bytes as `src`. The number of bytes is
201
+ // given by:
202
+ //
203
+ // (11) `dst_len = dl.offset + (dl.elem_size * dst_meta)`
204
+ //
205
+ // (12) `dst_len = dl.offset + (dl.elem_size * (self.offset_delta_elems
206
+ // + (self.elem_multiple * src_meta)))` (substituing this method's
207
+ // return value)
208
+ //
209
+ // (13) `dst_len = dl.offset + (dl.elem_size * ((src_len - dl.offset) /
210
+ // dl.elem_size))` (substituting (10))
211
+ //
212
+ // (14) `dst_len = dl.offset + (src_len - dl.offset)`
213
+ //
214
+ // (15) `dst_len = src_len`
215
+ //
216
+ // [1] TODO
217
+ //
218
+ // TODO: Clarify that this algorithm is agnostic to post-padding, and
219
+ // justify that this is acceptable.
220
+ #[ allow( clippy:: arithmetic_side_effects) ]
221
+ return self . offset_delta_elems + ( self . elem_multiple * src_meta) ;
222
+ }
223
+ }
224
+
95
225
impl DstLayout {
96
226
/// The minimum possible alignment of a type.
97
227
const MIN_ALIGN : NonZeroUsize = match NonZeroUsize :: new ( 1 ) {
@@ -202,6 +332,48 @@ impl DstLayout {
202
332
}
203
333
}
204
334
335
+ pub ( crate ) const fn try_compute_cast_params ( self , other : & DstLayout ) -> Option < CastParams > {
336
+ let ( slf, other) = if let ( SizeInfo :: SliceDst ( slf) , SizeInfo :: SliceDst ( other) ) =
337
+ ( self . size_info , other. size_info )
338
+ {
339
+ ( slf, other)
340
+ } else {
341
+ return None ;
342
+ } ;
343
+
344
+ let offset_delta = if let Some ( od) = slf. offset . checked_sub ( other. offset ) {
345
+ od
346
+ } else {
347
+ return None ;
348
+ } ;
349
+
350
+ let delta_mod_other_elem = offset_delta. checked_rem ( other. elem_size ) ;
351
+ let elem_remainder = slf. elem_size . checked_rem ( other. elem_size ) ;
352
+
353
+ let ( delta_mod_other_elem, elem_remainder) =
354
+ if let ( Some ( dmoe) , Some ( em) ) = ( delta_mod_other_elem, elem_remainder) {
355
+ ( dmoe, em)
356
+ } else {
357
+ return None ;
358
+ } ;
359
+
360
+ if delta_mod_other_elem != 0 || slf. elem_size < other. elem_size || elem_remainder != 0 {
361
+ return None ;
362
+ }
363
+
364
+ // PANICS: The preceding `slf.elem_size.checked_rem(other.elem_size)`
365
+ // could only return `Some` if `other.elem_size > 0`, so this division
366
+ // will not divide by zero.
367
+ #[ allow( clippy:: arithmetic_side_effects) ]
368
+ let offset_delta_elems = offset_delta / other. elem_size ;
369
+ // PANICS: See previous panics comment.
370
+ #[ allow( clippy:: arithmetic_side_effects) ]
371
+ let elem_multiple = slf. elem_size / other. elem_size ;
372
+
373
+ // INVARIANTS: TODO
374
+ Some ( CastParams { offset_delta_elems, elem_multiple } )
375
+ }
376
+
205
377
/// Like `Layout::extend`, this creates a layout that describes a record
206
378
/// whose layout consists of `self` followed by `next` that includes the
207
379
/// necessary inter-field padding, but not any trailing padding.
0 commit comments