|
| 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