@@ -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,28 @@ impl DstLayout {
202
332
}
203
333
}
204
334
335
+ pub ( crate ) const fn try_compute_cast_params ( self , other : & DstLayout ) -> Option < CastParams > {
336
+ // TODO: Document invariants
337
+ if let ( SizeInfo :: SliceDst ( slf) , SizeInfo :: SliceDst ( other) ) =
338
+ ( self . size_info , other. size_info )
339
+ {
340
+ if let Some ( offset_delta) = slf. offset . checked_sub ( other. offset ) {
341
+ if offset_delta % other. elem_size == 0
342
+ && other. elem_size > 0
343
+ && slf. elem_size >= other. elem_size
344
+ && slf. elem_size % other. elem_size == 0
345
+ {
346
+ return Some ( CastParams {
347
+ offset_delta_elems : offset_delta / other. elem_size ,
348
+ elem_multiple : slf. elem_size / other. elem_size ,
349
+ } ) ;
350
+ }
351
+ }
352
+ }
353
+
354
+ None
355
+ }
356
+
205
357
/// Like `Layout::extend`, this creates a layout that describes a record
206
358
/// whose layout consists of `self` followed by `next` that includes the
207
359
/// necessary inter-field padding, but not any trailing padding.
0 commit comments