|
| 1 | +use rustc_data_structures::stack::ensure_sufficient_stack; |
1 | 2 | use tracing::{debug, instrument, trace};
|
2 | 3 |
|
3 | 4 | pub(crate) mod query_context;
|
@@ -149,128 +150,135 @@ where
|
149 | 150 | if let Some(answer) = cache.get(&(src_state, dst_state)) {
|
150 | 151 | answer.clone()
|
151 | 152 | } else {
|
152 |
| - debug!(?src_state, ?dst_state); |
153 |
| - debug!(src = ?self.src); |
154 |
| - debug!(dst = ?self.dst); |
155 |
| - debug!( |
156 |
| - src_transitions_len = self.src.transitions.len(), |
157 |
| - dst_transitions_len = self.dst.transitions.len() |
158 |
| - ); |
159 |
| - let answer = if dst_state == self.dst.accept { |
160 |
| - // truncation: `size_of(Src) >= size_of(Dst)` |
161 |
| - // |
162 |
| - // Why is truncation OK to do? Because even though the Src is bigger, all we care about |
163 |
| - // is whether we have enough data for the Dst to be valid in accordance with what its |
164 |
| - // type dictates. |
165 |
| - // For example, in a u8 to `()` transmutation, we have enough data available from the u8 |
166 |
| - // to transmute it to a `()` (though in this case does `()` really need any data to |
167 |
| - // begin with? It doesn't). Same thing with u8 to fieldless struct. |
168 |
| - // Now then, why is something like u8 to bool not allowed? That is not because the bool |
169 |
| - // is smaller in size, but rather because those 2 bits that we are re-interpreting from |
170 |
| - // the u8 could introduce invalid states for the bool type. |
171 |
| - // |
172 |
| - // So, if it's possible to transmute to a smaller Dst by truncating, and we can guarantee |
173 |
| - // that none of the actually-used data can introduce an invalid state for Dst's type, we |
174 |
| - // are able to safely transmute, even with truncation. |
175 |
| - Answer::Yes |
176 |
| - } else if src_state == self.src.accept { |
177 |
| - // extension: `size_of(Src) <= size_of(Dst)` |
178 |
| - if let Some(dst_state_prime) = self.dst.get_uninit_edge_dst(dst_state) { |
179 |
| - self.answer_memo(cache, src_state, dst_state_prime) |
180 |
| - } else { |
181 |
| - Answer::No(Reason::DstIsTooBig) |
182 |
| - } |
| 153 | + let answer = ensure_sufficient_stack(|| self.answer_impl(cache, src_state, dst_state)); |
| 154 | + if let Some(..) = cache.insert((src_state, dst_state), answer.clone()) { |
| 155 | + panic!("failed to correctly cache transmutability") |
| 156 | + } |
| 157 | + answer |
| 158 | + } |
| 159 | + } |
| 160 | + |
| 161 | + fn answer_impl( |
| 162 | + &self, |
| 163 | + cache: &mut Map<(dfa::State, dfa::State), Answer<<C as QueryContext>::Ref>>, |
| 164 | + src_state: dfa::State, |
| 165 | + dst_state: dfa::State, |
| 166 | + ) -> Answer<<C as QueryContext>::Ref> { |
| 167 | + debug!(?src_state, ?dst_state); |
| 168 | + debug!(src = ?self.src); |
| 169 | + debug!(dst = ?self.dst); |
| 170 | + debug!( |
| 171 | + src_transitions_len = self.src.transitions.len(), |
| 172 | + dst_transitions_len = self.dst.transitions.len() |
| 173 | + ); |
| 174 | + if dst_state == self.dst.accept { |
| 175 | + // truncation: `size_of(Src) >= size_of(Dst)` |
| 176 | + // |
| 177 | + // Why is truncation OK to do? Because even though the Src is bigger, all we care about |
| 178 | + // is whether we have enough data for the Dst to be valid in accordance with what its |
| 179 | + // type dictates. |
| 180 | + // For example, in a u8 to `()` transmutation, we have enough data available from the u8 |
| 181 | + // to transmute it to a `()` (though in this case does `()` really need any data to |
| 182 | + // begin with? It doesn't). Same thing with u8 to fieldless struct. |
| 183 | + // Now then, why is something like u8 to bool not allowed? That is not because the bool |
| 184 | + // is smaller in size, but rather because those 2 bits that we are re-interpreting from |
| 185 | + // the u8 could introduce invalid states for the bool type. |
| 186 | + // |
| 187 | + // So, if it's possible to transmute to a smaller Dst by truncating, and we can guarantee |
| 188 | + // that none of the actually-used data can introduce an invalid state for Dst's type, we |
| 189 | + // are able to safely transmute, even with truncation. |
| 190 | + Answer::Yes |
| 191 | + } else if src_state == self.src.accept { |
| 192 | + // extension: `size_of(Src) <= size_of(Dst)` |
| 193 | + if let Some(dst_state_prime) = self.dst.get_uninit_edge_dst(dst_state) { |
| 194 | + self.answer_memo(cache, src_state, dst_state_prime) |
| 195 | + } else { |
| 196 | + Answer::No(Reason::DstIsTooBig) |
| 197 | + } |
| 198 | + } else { |
| 199 | + let src_quantifier = if self.assume.validity { |
| 200 | + // if the compiler may assume that the programmer is doing additional validity checks, |
| 201 | + // (e.g.: that `src != 3u8` when the destination type is `bool`) |
| 202 | + // then there must exist at least one transition out of `src_state` such that the transmute is viable... |
| 203 | + Quantifier::ThereExists |
183 | 204 | } else {
|
184 |
| - let src_quantifier = if self.assume.validity { |
185 |
| - // if the compiler may assume that the programmer is doing additional validity checks, |
186 |
| - // (e.g.: that `src != 3u8` when the destination type is `bool`) |
187 |
| - // then there must exist at least one transition out of `src_state` such that the transmute is viable... |
188 |
| - Quantifier::ThereExists |
189 |
| - } else { |
190 |
| - // if the compiler cannot assume that the programmer is doing additional validity checks, |
191 |
| - // then for all transitions out of `src_state`, such that the transmute is viable... |
192 |
| - // then there must exist at least one transition out of `dst_state` such that the transmute is viable... |
193 |
| - Quantifier::ForAll |
194 |
| - }; |
195 |
| - |
196 |
| - let bytes_answer = src_quantifier.apply( |
197 |
| - union(self.src.bytes_from(src_state), self.dst.bytes_from(dst_state)) |
198 |
| - .filter_map(|(_range, (src_state_prime, dst_state_prime))| { |
199 |
| - match (src_state_prime, dst_state_prime) { |
200 |
| - // No matching transitions in `src`. Skip. |
201 |
| - (None, _) => None, |
202 |
| - // No matching transitions in `dst`. Fail. |
203 |
| - (Some(_), None) => Some(Answer::No(Reason::DstIsBitIncompatible)), |
204 |
| - // Matching transitions. Continue with successor states. |
205 |
| - (Some(src_state_prime), Some(dst_state_prime)) => { |
206 |
| - Some(self.answer_memo(cache, src_state_prime, dst_state_prime)) |
207 |
| - } |
| 205 | + // if the compiler cannot assume that the programmer is doing additional validity checks, |
| 206 | + // then for all transitions out of `src_state`, such that the transmute is viable... |
| 207 | + // then there must exist at least one transition out of `dst_state` such that the transmute is viable... |
| 208 | + Quantifier::ForAll |
| 209 | + }; |
| 210 | + |
| 211 | + let bytes_answer = src_quantifier.apply( |
| 212 | + union(self.src.bytes_from(src_state), self.dst.bytes_from(dst_state)).filter_map( |
| 213 | + |(_range, (src_state_prime, dst_state_prime))| { |
| 214 | + match (src_state_prime, dst_state_prime) { |
| 215 | + // No matching transitions in `src`. Skip. |
| 216 | + (None, _) => None, |
| 217 | + // No matching transitions in `dst`. Fail. |
| 218 | + (Some(_), None) => Some(Answer::No(Reason::DstIsBitIncompatible)), |
| 219 | + // Matching transitions. Continue with successor states. |
| 220 | + (Some(src_state_prime), Some(dst_state_prime)) => { |
| 221 | + Some(self.answer_memo(cache, src_state_prime, dst_state_prime)) |
208 | 222 | }
|
209 |
| - }), |
210 |
| - ); |
211 |
| - |
212 |
| - // The below early returns reflect how this code would behave: |
213 |
| - // if self.assume.validity { |
214 |
| - // or(bytes_answer, refs_answer) |
215 |
| - // } else { |
216 |
| - // and(bytes_answer, refs_answer) |
217 |
| - // } |
218 |
| - // ...if `refs_answer` was computed lazily. The below early |
219 |
| - // returns can be deleted without impacting the correctness of |
220 |
| - // the algorithm; only its performance. |
221 |
| - debug!(?bytes_answer); |
222 |
| - match bytes_answer { |
223 |
| - Answer::No(_) if !self.assume.validity => return bytes_answer, |
224 |
| - Answer::Yes if self.assume.validity => return bytes_answer, |
225 |
| - _ => {} |
226 |
| - }; |
227 |
| - |
228 |
| - let refs_answer = src_quantifier.apply( |
229 |
| - // for each reference transition out of `src_state`... |
230 |
| - self.src.refs_from(src_state).map(|(src_ref, src_state_prime)| { |
231 |
| - // ...there exists a reference transition out of `dst_state`... |
232 |
| - Quantifier::ThereExists.apply(self.dst.refs_from(dst_state).map( |
233 |
| - |(dst_ref, dst_state_prime)| { |
234 |
| - if !src_ref.is_mutable() && dst_ref.is_mutable() { |
235 |
| - Answer::No(Reason::DstIsMoreUnique) |
236 |
| - } else if !self.assume.alignment |
237 |
| - && src_ref.min_align() < dst_ref.min_align() |
238 |
| - { |
239 |
| - Answer::No(Reason::DstHasStricterAlignment { |
240 |
| - src_min_align: src_ref.min_align(), |
241 |
| - dst_min_align: dst_ref.min_align(), |
242 |
| - }) |
243 |
| - } else if dst_ref.size() > src_ref.size() { |
244 |
| - Answer::No(Reason::DstRefIsTooBig { |
| 223 | + } |
| 224 | + }, |
| 225 | + ), |
| 226 | + ); |
| 227 | + |
| 228 | + // The below early returns reflect how this code would behave: |
| 229 | + // if self.assume.validity { |
| 230 | + // or(bytes_answer, refs_answer) |
| 231 | + // } else { |
| 232 | + // and(bytes_answer, refs_answer) |
| 233 | + // } |
| 234 | + // ...if `refs_answer` was computed lazily. The below early |
| 235 | + // returns can be deleted without impacting the correctness of |
| 236 | + // the algorithm; only its performance. |
| 237 | + debug!(?bytes_answer); |
| 238 | + match bytes_answer { |
| 239 | + Answer::No(_) if !self.assume.validity => return bytes_answer, |
| 240 | + Answer::Yes if self.assume.validity => return bytes_answer, |
| 241 | + _ => {} |
| 242 | + }; |
| 243 | + |
| 244 | + let refs_answer = src_quantifier.apply( |
| 245 | + // for each reference transition out of `src_state`... |
| 246 | + self.src.refs_from(src_state).map(|(src_ref, src_state_prime)| { |
| 247 | + // ...there exists a reference transition out of `dst_state`... |
| 248 | + Quantifier::ThereExists.apply(self.dst.refs_from(dst_state).map( |
| 249 | + |(dst_ref, dst_state_prime)| { |
| 250 | + if !src_ref.is_mutable() && dst_ref.is_mutable() { |
| 251 | + Answer::No(Reason::DstIsMoreUnique) |
| 252 | + } else if !self.assume.alignment |
| 253 | + && src_ref.min_align() < dst_ref.min_align() |
| 254 | + { |
| 255 | + Answer::No(Reason::DstHasStricterAlignment { |
| 256 | + src_min_align: src_ref.min_align(), |
| 257 | + dst_min_align: dst_ref.min_align(), |
| 258 | + }) |
| 259 | + } else if dst_ref.size() > src_ref.size() { |
| 260 | + Answer::No(Reason::DstRefIsTooBig { src: src_ref, dst: dst_ref }) |
| 261 | + } else { |
| 262 | + // ...such that `src` is transmutable into `dst`, if |
| 263 | + // `src_ref` is transmutability into `dst_ref`. |
| 264 | + and( |
| 265 | + Answer::If(Condition::IfTransmutable { |
245 | 266 | src: src_ref,
|
246 | 267 | dst: dst_ref,
|
247 |
| - }) |
248 |
| - } else { |
249 |
| - // ...such that `src` is transmutable into `dst`, if |
250 |
| - // `src_ref` is transmutability into `dst_ref`. |
251 |
| - and( |
252 |
| - Answer::If(Condition::IfTransmutable { |
253 |
| - src: src_ref, |
254 |
| - dst: dst_ref, |
255 |
| - }), |
256 |
| - self.answer_memo(cache, src_state_prime, dst_state_prime), |
257 |
| - ) |
258 |
| - } |
259 |
| - }, |
260 |
| - )) |
261 |
| - }), |
262 |
| - ); |
263 |
| - |
264 |
| - if self.assume.validity { |
265 |
| - or(bytes_answer, refs_answer) |
266 |
| - } else { |
267 |
| - and(bytes_answer, refs_answer) |
268 |
| - } |
269 |
| - }; |
270 |
| - if let Some(..) = cache.insert((src_state, dst_state), answer.clone()) { |
271 |
| - panic!("failed to correctly cache transmutability") |
| 268 | + }), |
| 269 | + self.answer_memo(cache, src_state_prime, dst_state_prime), |
| 270 | + ) |
| 271 | + } |
| 272 | + }, |
| 273 | + )) |
| 274 | + }), |
| 275 | + ); |
| 276 | + |
| 277 | + if self.assume.validity { |
| 278 | + or(bytes_answer, refs_answer) |
| 279 | + } else { |
| 280 | + and(bytes_answer, refs_answer) |
272 | 281 | }
|
273 |
| - answer |
274 | 282 | }
|
275 | 283 | }
|
276 | 284 | }
|
|
0 commit comments