1 //! A framework that can express both [gen-kill] and generic dataflow problems. 2 //! 3 //! To actually use this framework, you must implement either the `Analysis` or the 4 //! `GenKillAnalysis` trait. If your transfer function can be expressed with only gen/kill 5 //! operations, prefer `GenKillAnalysis` since it will run faster while iterating to fixpoint. The 6 //! `impls` module contains several examples of gen/kill dataflow analyses. 7 //! 8 //! Create an `Engine` for your analysis using the `into_engine` method on the `Analysis` trait, 9 //! then call `iterate_to_fixpoint`. From there, you can use a `ResultsCursor` to inspect the 10 //! fixpoint solution to your dataflow problem, or implement the `ResultsVisitor` interface and use 11 //! `visit_results`. The following example uses the `ResultsCursor` approach. 12 //! 13 //! ```ignore (cross-crate-imports) 14 //! use rustc_const_eval::dataflow::Analysis; // Makes `into_engine` available. 15 //! 16 //! fn do_my_analysis(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) { 17 //! let analysis = MyAnalysis::new() 18 //! .into_engine(tcx, body) 19 //! .iterate_to_fixpoint() 20 //! .into_results_cursor(body); 21 //! 22 //! // Print the dataflow state *after* each statement in the start block. 23 //! for (_, statement_index) in body.block_data[START_BLOCK].statements.iter_enumerated() { 24 //! cursor.seek_after(Location { block: START_BLOCK, statement_index }); 25 //! let state = cursor.get(); 26 //! println!("{:?}", state); 27 //! } 28 //! } 29 //! ``` 30 //! 31 //! [gen-kill]: https://en.wikipedia.org/wiki/Data-flow_analysis#Bit_vector_problems 32 33 use std::borrow::BorrowMut; 34 use std::cmp::Ordering; 35 36 use rustc_index::bit_set::{BitSet, HybridBitSet}; 37 use rustc_index::vec::Idx; 38 use rustc_middle::mir::{self, BasicBlock, Location}; 39 use rustc_middle::ty::TyCtxt; 40 41 mod cursor; 42 mod direction; 43 mod engine; 44 pub mod fmt; 45 pub mod graphviz; 46 pub mod lattice; 47 mod visitor; 48 49 pub use self::cursor::{ResultsCursor, ResultsRefCursor}; 50 pub use self::direction::{Backward, Direction, Forward}; 51 pub use self::engine::{Engine, Results}; 52 pub use self::lattice::{JoinSemiLattice, MeetSemiLattice}; 53 pub use self::visitor::{visit_results, ResultsVisitable, ResultsVisitor}; 54 55 /// Define the domain of a dataflow problem. 56 /// 57 /// This trait specifies the lattice on which this analysis operates (the domain) as well as its 58 /// initial value at the entry point of each basic block. 59 pub trait AnalysisDomain<'tcx> { 60 /// The type that holds the dataflow state at any given point in the program. 61 type Domain: Clone + JoinSemiLattice; 62 63 /// The direction of this analysis. Either `Forward` or `Backward`. 64 type Direction: Direction = Forward; 65 66 /// A descriptive name for this analysis. Used only for debugging. 67 /// 68 /// This name should be brief and contain no spaces, periods or other characters that are not 69 /// suitable as part of a filename. 70 const NAME: &'static str; 71 72 /// The initial value of the dataflow state upon entry to each basic block. bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain73 fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain; 74 75 /// Mutates the initial value of the dataflow state upon entry to the `START_BLOCK`. 76 /// 77 /// For backward analyses, initial state besides the bottom value is not yet supported. Trying 78 /// to mutate the initial state will result in a panic. 79 // 80 // FIXME: For backward dataflow analyses, the initial state should be applied to every basic 81 // block where control flow could exit the MIR body (e.g., those terminated with `return` or 82 // `resume`). It's not obvious how to handle `yield` points in generators, however. initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain)83 fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain); 84 } 85 86 /// A dataflow problem with an arbitrarily complex transfer function. 87 /// 88 /// # Convergence 89 /// 90 /// When implementing this trait directly (not via [`GenKillAnalysis`]), it's possible to choose a 91 /// transfer function such that the analysis does not reach fixpoint. To guarantee convergence, 92 /// your transfer functions must maintain the following invariant: 93 /// 94 /// > If the dataflow state **before** some point in the program changes to be greater 95 /// than the prior state **before** that point, the dataflow state **after** that point must 96 /// also change to be greater than the prior state **after** that point. 97 /// 98 /// This invariant guarantees that the dataflow state at a given point in the program increases 99 /// monotonically until fixpoint is reached. Note that this monotonicity requirement only applies 100 /// to the same point in the program at different points in time. The dataflow state at a given 101 /// point in the program may or may not be greater than the state at any preceding point. 102 pub trait Analysis<'tcx>: AnalysisDomain<'tcx> { 103 /// Updates the current dataflow state with the effect of evaluating a statement. apply_statement_effect( &self, state: &mut Self::Domain, statement: &mir::Statement<'tcx>, location: Location, )104 fn apply_statement_effect( 105 &self, 106 state: &mut Self::Domain, 107 statement: &mir::Statement<'tcx>, 108 location: Location, 109 ); 110 111 /// Updates the current dataflow state with an effect that occurs immediately *before* the 112 /// given statement. 113 /// 114 /// This method is useful if the consumer of the results of this analysis needs only to observe 115 /// *part* of the effect of a statement (e.g. for two-phase borrows). As a general rule, 116 /// analyses should not implement this without implementing `apply_statement_effect`. apply_before_statement_effect( &self, _state: &mut Self::Domain, _statement: &mir::Statement<'tcx>, _location: Location, )117 fn apply_before_statement_effect( 118 &self, 119 _state: &mut Self::Domain, 120 _statement: &mir::Statement<'tcx>, 121 _location: Location, 122 ) { 123 } 124 125 /// Updates the current dataflow state with the effect of evaluating a terminator. 126 /// 127 /// The effect of a successful return from a `Call` terminator should **not** be accounted for 128 /// in this function. That should go in `apply_call_return_effect`. For example, in the 129 /// `InitializedPlaces` analyses, the return place for a function call is not marked as 130 /// initialized here. apply_terminator_effect( &self, state: &mut Self::Domain, terminator: &mir::Terminator<'tcx>, location: Location, )131 fn apply_terminator_effect( 132 &self, 133 state: &mut Self::Domain, 134 terminator: &mir::Terminator<'tcx>, 135 location: Location, 136 ); 137 138 /// Updates the current dataflow state with an effect that occurs immediately *before* the 139 /// given terminator. 140 /// 141 /// This method is useful if the consumer of the results of this analysis needs only to observe 142 /// *part* of the effect of a terminator (e.g. for two-phase borrows). As a general rule, 143 /// analyses should not implement this without implementing `apply_terminator_effect`. apply_before_terminator_effect( &self, _state: &mut Self::Domain, _terminator: &mir::Terminator<'tcx>, _location: Location, )144 fn apply_before_terminator_effect( 145 &self, 146 _state: &mut Self::Domain, 147 _terminator: &mir::Terminator<'tcx>, 148 _location: Location, 149 ) { 150 } 151 152 /* Edge-specific effects */ 153 154 /// Updates the current dataflow state with the effect of a successful return from a `Call` 155 /// terminator. 156 /// 157 /// This is separate from `apply_terminator_effect` to properly track state across unwind 158 /// edges. apply_call_return_effect( &self, state: &mut Self::Domain, block: BasicBlock, func: &mir::Operand<'tcx>, args: &[mir::Operand<'tcx>], return_place: mir::Place<'tcx>, )159 fn apply_call_return_effect( 160 &self, 161 state: &mut Self::Domain, 162 block: BasicBlock, 163 func: &mir::Operand<'tcx>, 164 args: &[mir::Operand<'tcx>], 165 return_place: mir::Place<'tcx>, 166 ); 167 168 /// Updates the current dataflow state with the effect of resuming from a `Yield` terminator. 169 /// 170 /// This is similar to `apply_call_return_effect` in that it only takes place after the 171 /// generator is resumed, not when it is dropped. 172 /// 173 /// By default, no effects happen. apply_yield_resume_effect( &self, _state: &mut Self::Domain, _resume_block: BasicBlock, _resume_place: mir::Place<'tcx>, )174 fn apply_yield_resume_effect( 175 &self, 176 _state: &mut Self::Domain, 177 _resume_block: BasicBlock, 178 _resume_place: mir::Place<'tcx>, 179 ) { 180 } 181 182 /// Updates the current dataflow state with the effect of taking a particular branch in a 183 /// `SwitchInt` terminator. 184 /// 185 /// Unlike the other edge-specific effects, which are allowed to mutate `Self::Domain` 186 /// directly, overriders of this method must pass a callback to 187 /// `SwitchIntEdgeEffects::apply`. The callback will be run once for each outgoing edge and 188 /// will have access to the dataflow state that will be propagated along that edge. 189 /// 190 /// This interface is somewhat more complex than the other visitor-like "effect" methods. 191 /// However, it is both more ergonomic—callers don't need to recompute or cache information 192 /// about a given `SwitchInt` terminator for each one of its edges—and more efficient—the 193 /// engine doesn't need to clone the exit state for a block unless 194 /// `SwitchIntEdgeEffects::apply` is actually called. 195 /// 196 /// FIXME: This class of effects is not supported for backward dataflow analyses. apply_switch_int_edge_effects( &self, _block: BasicBlock, _discr: &mir::Operand<'tcx>, _apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>, )197 fn apply_switch_int_edge_effects( 198 &self, 199 _block: BasicBlock, 200 _discr: &mir::Operand<'tcx>, 201 _apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>, 202 ) { 203 } 204 205 /* Extension methods */ 206 207 /// Creates an `Engine` to find the fixpoint for this dataflow problem. 208 /// 209 /// You shouldn't need to override this outside this module, since the combination of the 210 /// default impl and the one for all `A: GenKillAnalysis` will do the right thing. 211 /// Its purpose is to enable method chaining like so: 212 /// 213 /// ```ignore (cross-crate-imports) 214 /// let results = MyAnalysis::new(tcx, body) 215 /// .into_engine(tcx, body, def_id) 216 /// .iterate_to_fixpoint() 217 /// .into_results_cursor(body); 218 /// ``` into_engine(self, tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>) -> Engine<'mir, 'tcx, Self> where Self: Sized,219 fn into_engine(self, tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>) -> Engine<'mir, 'tcx, Self> 220 where 221 Self: Sized, 222 { 223 Engine::new_generic(tcx, body, self) 224 } 225 } 226 227 /// A gen/kill dataflow problem. 228 /// 229 /// Each method in this trait has a corresponding one in `Analysis`. However, these methods only 230 /// allow modification of the dataflow state via "gen" and "kill" operations. By defining transfer 231 /// functions for each statement in this way, the transfer function for an entire basic block can 232 /// be computed efficiently. 233 /// 234 /// `Analysis` is automatically implemented for all implementers of `GenKillAnalysis`. 235 pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> { 236 type Idx: Idx; 237 238 /// See `Analysis::apply_statement_effect`. statement_effect( &self, trans: &mut impl GenKill<Self::Idx>, statement: &mir::Statement<'tcx>, location: Location, )239 fn statement_effect( 240 &self, 241 trans: &mut impl GenKill<Self::Idx>, 242 statement: &mir::Statement<'tcx>, 243 location: Location, 244 ); 245 246 /// See `Analysis::apply_before_statement_effect`. before_statement_effect( &self, _trans: &mut impl GenKill<Self::Idx>, _statement: &mir::Statement<'tcx>, _location: Location, )247 fn before_statement_effect( 248 &self, 249 _trans: &mut impl GenKill<Self::Idx>, 250 _statement: &mir::Statement<'tcx>, 251 _location: Location, 252 ) { 253 } 254 255 /// See `Analysis::apply_terminator_effect`. terminator_effect( &self, trans: &mut impl GenKill<Self::Idx>, terminator: &mir::Terminator<'tcx>, location: Location, )256 fn terminator_effect( 257 &self, 258 trans: &mut impl GenKill<Self::Idx>, 259 terminator: &mir::Terminator<'tcx>, 260 location: Location, 261 ); 262 263 /// See `Analysis::apply_before_terminator_effect`. before_terminator_effect( &self, _trans: &mut impl GenKill<Self::Idx>, _terminator: &mir::Terminator<'tcx>, _location: Location, )264 fn before_terminator_effect( 265 &self, 266 _trans: &mut impl GenKill<Self::Idx>, 267 _terminator: &mir::Terminator<'tcx>, 268 _location: Location, 269 ) { 270 } 271 272 /* Edge-specific effects */ 273 274 /// See `Analysis::apply_call_return_effect`. call_return_effect( &self, trans: &mut impl GenKill<Self::Idx>, block: BasicBlock, func: &mir::Operand<'tcx>, args: &[mir::Operand<'tcx>], return_place: mir::Place<'tcx>, )275 fn call_return_effect( 276 &self, 277 trans: &mut impl GenKill<Self::Idx>, 278 block: BasicBlock, 279 func: &mir::Operand<'tcx>, 280 args: &[mir::Operand<'tcx>], 281 return_place: mir::Place<'tcx>, 282 ); 283 284 /// See `Analysis::apply_yield_resume_effect`. yield_resume_effect( &self, _trans: &mut impl GenKill<Self::Idx>, _resume_block: BasicBlock, _resume_place: mir::Place<'tcx>, )285 fn yield_resume_effect( 286 &self, 287 _trans: &mut impl GenKill<Self::Idx>, 288 _resume_block: BasicBlock, 289 _resume_place: mir::Place<'tcx>, 290 ) { 291 } 292 293 /// See `Analysis::apply_switch_int_edge_effects`. switch_int_edge_effects<G: GenKill<Self::Idx>>( &self, _block: BasicBlock, _discr: &mir::Operand<'tcx>, _edge_effects: &mut impl SwitchIntEdgeEffects<G>, )294 fn switch_int_edge_effects<G: GenKill<Self::Idx>>( 295 &self, 296 _block: BasicBlock, 297 _discr: &mir::Operand<'tcx>, 298 _edge_effects: &mut impl SwitchIntEdgeEffects<G>, 299 ) { 300 } 301 } 302 303 impl<A> Analysis<'tcx> for A 304 where 305 A: GenKillAnalysis<'tcx>, 306 A::Domain: GenKill<A::Idx> + BorrowMut<BitSet<A::Idx>>, 307 { apply_statement_effect( &self, state: &mut A::Domain, statement: &mir::Statement<'tcx>, location: Location, )308 fn apply_statement_effect( 309 &self, 310 state: &mut A::Domain, 311 statement: &mir::Statement<'tcx>, 312 location: Location, 313 ) { 314 self.statement_effect(state, statement, location); 315 } 316 apply_before_statement_effect( &self, state: &mut A::Domain, statement: &mir::Statement<'tcx>, location: Location, )317 fn apply_before_statement_effect( 318 &self, 319 state: &mut A::Domain, 320 statement: &mir::Statement<'tcx>, 321 location: Location, 322 ) { 323 self.before_statement_effect(state, statement, location); 324 } 325 apply_terminator_effect( &self, state: &mut A::Domain, terminator: &mir::Terminator<'tcx>, location: Location, )326 fn apply_terminator_effect( 327 &self, 328 state: &mut A::Domain, 329 terminator: &mir::Terminator<'tcx>, 330 location: Location, 331 ) { 332 self.terminator_effect(state, terminator, location); 333 } 334 apply_before_terminator_effect( &self, state: &mut A::Domain, terminator: &mir::Terminator<'tcx>, location: Location, )335 fn apply_before_terminator_effect( 336 &self, 337 state: &mut A::Domain, 338 terminator: &mir::Terminator<'tcx>, 339 location: Location, 340 ) { 341 self.before_terminator_effect(state, terminator, location); 342 } 343 344 /* Edge-specific effects */ 345 apply_call_return_effect( &self, state: &mut A::Domain, block: BasicBlock, func: &mir::Operand<'tcx>, args: &[mir::Operand<'tcx>], return_place: mir::Place<'tcx>, )346 fn apply_call_return_effect( 347 &self, 348 state: &mut A::Domain, 349 block: BasicBlock, 350 func: &mir::Operand<'tcx>, 351 args: &[mir::Operand<'tcx>], 352 return_place: mir::Place<'tcx>, 353 ) { 354 self.call_return_effect(state, block, func, args, return_place); 355 } 356 apply_yield_resume_effect( &self, state: &mut A::Domain, resume_block: BasicBlock, resume_place: mir::Place<'tcx>, )357 fn apply_yield_resume_effect( 358 &self, 359 state: &mut A::Domain, 360 resume_block: BasicBlock, 361 resume_place: mir::Place<'tcx>, 362 ) { 363 self.yield_resume_effect(state, resume_block, resume_place); 364 } 365 apply_switch_int_edge_effects( &self, block: BasicBlock, discr: &mir::Operand<'tcx>, edge_effects: &mut impl SwitchIntEdgeEffects<A::Domain>, )366 fn apply_switch_int_edge_effects( 367 &self, 368 block: BasicBlock, 369 discr: &mir::Operand<'tcx>, 370 edge_effects: &mut impl SwitchIntEdgeEffects<A::Domain>, 371 ) { 372 self.switch_int_edge_effects(block, discr, edge_effects); 373 } 374 375 /* Extension methods */ 376 into_engine(self, tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>) -> Engine<'mir, 'tcx, Self> where Self: Sized,377 fn into_engine(self, tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>) -> Engine<'mir, 'tcx, Self> 378 where 379 Self: Sized, 380 { 381 Engine::new_gen_kill(tcx, body, self) 382 } 383 } 384 385 /// The legal operations for a transfer function in a gen/kill problem. 386 /// 387 /// This abstraction exists because there are two different contexts in which we call the methods in 388 /// `GenKillAnalysis`. Sometimes we need to store a single transfer function that can be efficiently 389 /// applied multiple times, such as when computing the cumulative transfer function for each block. 390 /// These cases require a `GenKillSet`, which in turn requires two `BitSet`s of storage. Oftentimes, 391 /// however, we only need to apply an effect once. In *these* cases, it is more efficient to pass the 392 /// `BitSet` representing the state vector directly into the `*_effect` methods as opposed to 393 /// building up a `GenKillSet` and then throwing it away. 394 pub trait GenKill<T> { 395 /// Inserts `elem` into the state vector. gen(&mut self, elem: T)396 fn gen(&mut self, elem: T); 397 398 /// Removes `elem` from the state vector. kill(&mut self, elem: T)399 fn kill(&mut self, elem: T); 400 401 /// Calls `gen` for each element in `elems`. gen_all(&mut self, elems: impl IntoIterator<Item = T>)402 fn gen_all(&mut self, elems: impl IntoIterator<Item = T>) { 403 for elem in elems { 404 self.gen(elem); 405 } 406 } 407 408 /// Calls `kill` for each element in `elems`. kill_all(&mut self, elems: impl IntoIterator<Item = T>)409 fn kill_all(&mut self, elems: impl IntoIterator<Item = T>) { 410 for elem in elems { 411 self.kill(elem); 412 } 413 } 414 } 415 416 /// Stores a transfer function for a gen/kill problem. 417 /// 418 /// Calling `gen`/`kill` on a `GenKillSet` will "build up" a transfer function so that it can be 419 /// applied multiple times efficiently. When there are multiple calls to `gen` and/or `kill` for 420 /// the same element, the most recent one takes precedence. 421 #[derive(Clone)] 422 pub struct GenKillSet<T> { 423 gen: HybridBitSet<T>, 424 kill: HybridBitSet<T>, 425 } 426 427 impl<T: Idx> GenKillSet<T> { 428 /// Creates a new transfer function that will leave the dataflow state unchanged. identity(universe: usize) -> Self429 pub fn identity(universe: usize) -> Self { 430 GenKillSet { 431 gen: HybridBitSet::new_empty(universe), 432 kill: HybridBitSet::new_empty(universe), 433 } 434 } 435 apply(&self, state: &mut BitSet<T>)436 pub fn apply(&self, state: &mut BitSet<T>) { 437 state.union(&self.gen); 438 state.subtract(&self.kill); 439 } 440 } 441 442 impl<T: Idx> GenKill<T> for GenKillSet<T> { gen(&mut self, elem: T)443 fn gen(&mut self, elem: T) { 444 self.gen.insert(elem); 445 self.kill.remove(elem); 446 } 447 kill(&mut self, elem: T)448 fn kill(&mut self, elem: T) { 449 self.kill.insert(elem); 450 self.gen.remove(elem); 451 } 452 } 453 454 impl<T: Idx> GenKill<T> for BitSet<T> { gen(&mut self, elem: T)455 fn gen(&mut self, elem: T) { 456 self.insert(elem); 457 } 458 kill(&mut self, elem: T)459 fn kill(&mut self, elem: T) { 460 self.remove(elem); 461 } 462 } 463 464 impl<T: Idx> GenKill<T> for lattice::Dual<BitSet<T>> { gen(&mut self, elem: T)465 fn gen(&mut self, elem: T) { 466 self.0.insert(elem); 467 } 468 kill(&mut self, elem: T)469 fn kill(&mut self, elem: T) { 470 self.0.remove(elem); 471 } 472 } 473 474 // NOTE: DO NOT CHANGE VARIANT ORDER. The derived `Ord` impls rely on the current order. 475 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 476 pub enum Effect { 477 /// The "before" effect (e.g., `apply_before_statement_effect`) for a statement (or 478 /// terminator). 479 Before, 480 481 /// The "primary" effect (e.g., `apply_statement_effect`) for a statement (or terminator). 482 Primary, 483 } 484 485 impl Effect { at_index(self, statement_index: usize) -> EffectIndex486 pub const fn at_index(self, statement_index: usize) -> EffectIndex { 487 EffectIndex { effect: self, statement_index } 488 } 489 } 490 491 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 492 pub struct EffectIndex { 493 statement_index: usize, 494 effect: Effect, 495 } 496 497 impl EffectIndex { next_in_forward_order(self) -> Self498 fn next_in_forward_order(self) -> Self { 499 match self.effect { 500 Effect::Before => Effect::Primary.at_index(self.statement_index), 501 Effect::Primary => Effect::Before.at_index(self.statement_index + 1), 502 } 503 } 504 next_in_backward_order(self) -> Self505 fn next_in_backward_order(self) -> Self { 506 match self.effect { 507 Effect::Before => Effect::Primary.at_index(self.statement_index), 508 Effect::Primary => Effect::Before.at_index(self.statement_index - 1), 509 } 510 } 511 512 /// Returns `true` if the effect at `self` should be applied earlier than the effect at `other` 513 /// in forward order. precedes_in_forward_order(self, other: Self) -> bool514 fn precedes_in_forward_order(self, other: Self) -> bool { 515 let ord = self 516 .statement_index 517 .cmp(&other.statement_index) 518 .then_with(|| self.effect.cmp(&other.effect)); 519 ord == Ordering::Less 520 } 521 522 /// Returns `true` if the effect at `self` should be applied earlier than the effect at `other` 523 /// in backward order. precedes_in_backward_order(self, other: Self) -> bool524 fn precedes_in_backward_order(self, other: Self) -> bool { 525 let ord = other 526 .statement_index 527 .cmp(&self.statement_index) 528 .then_with(|| self.effect.cmp(&other.effect)); 529 ord == Ordering::Less 530 } 531 } 532 533 pub struct SwitchIntTarget { 534 pub value: Option<u128>, 535 pub target: BasicBlock, 536 } 537 538 /// A type that records the edge-specific effects for a `SwitchInt` terminator. 539 pub trait SwitchIntEdgeEffects<D> { 540 /// Calls `apply_edge_effect` for each outgoing edge from a `SwitchInt` terminator and 541 /// records the results. apply(&mut self, apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget))542 fn apply(&mut self, apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget)); 543 } 544 545 #[cfg(test)] 546 mod tests; 547