]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/dataflow/impls/mod.rs
Remove bitslice.rs.
[rust.git] / src / librustc_mir / dataflow / impls / mod.rs
1 // Copyright 2012-2016 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! Dataflow analyses are built upon some interpretation of the
12 //! bitvectors attached to each basic block, represented via a
13 //! zero-sized structure.
14
15 use rustc::ty::TyCtxt;
16 use rustc::mir::{self, Mir, Location};
17 use rustc_data_structures::bitvec::{BitwiseOperator, Word};
18 use rustc_data_structures::indexed_set::{IdxSet};
19 use rustc_data_structures::indexed_vec::Idx;
20
21 use super::MoveDataParamEnv;
22
23 use util::elaborate_drops::DropFlagState;
24
25 use super::move_paths::{HasMoveData, MoveData, MovePathIndex, InitIndex};
26 use super::move_paths::{LookupResult, InitKind};
27 use super::{BitDenotation, BlockSets, InitialFlow};
28
29 use super::drop_flag_effects_for_function_entry;
30 use super::drop_flag_effects_for_location;
31 use super::on_lookup_result_bits;
32
33 mod storage_liveness;
34
35 pub use self::storage_liveness::*;
36
37 mod borrowed_locals;
38
39 pub use self::borrowed_locals::*;
40
41 pub(super) mod borrows;
42
43 /// `MaybeInitializedPlaces` tracks all places that might be
44 /// initialized upon reaching a particular point in the control flow
45 /// for a function.
46 ///
47 /// For example, in code like the following, we have corresponding
48 /// dataflow information shown in the right-hand comments.
49 ///
50 /// ```rust
51 /// struct S;
52 /// fn foo(pred: bool) {                       // maybe-init:
53 ///                                            // {}
54 ///     let a = S; let b = S; let c; let d;    // {a, b}
55 ///
56 ///     if pred {
57 ///         drop(a);                           // {   b}
58 ///         b = S;                             // {   b}
59 ///
60 ///     } else {
61 ///         drop(b);                           // {a}
62 ///         d = S;                             // {a,       d}
63 ///
64 ///     }                                      // {a, b,    d}
65 ///
66 ///     c = S;                                 // {a, b, c, d}
67 /// }
68 /// ```
69 ///
70 /// To determine whether a place *must* be initialized at a
71 /// particular control-flow point, one can take the set-difference
72 /// between this data and the data from `MaybeUninitializedPlaces` at the
73 /// corresponding control-flow point.
74 ///
75 /// Similarly, at a given `drop` statement, the set-intersection
76 /// between this data and `MaybeUninitializedPlaces` yields the set of
77 /// places that would require a dynamic drop-flag at that statement.
78 pub struct MaybeInitializedPlaces<'a, 'gcx: 'tcx, 'tcx: 'a> {
79     tcx: TyCtxt<'a, 'gcx, 'tcx>,
80     mir: &'a Mir<'tcx>,
81     mdpe: &'a MoveDataParamEnv<'gcx, 'tcx>,
82 }
83
84 impl<'a, 'gcx: 'tcx, 'tcx> MaybeInitializedPlaces<'a, 'gcx, 'tcx> {
85     pub fn new(tcx: TyCtxt<'a, 'gcx, 'tcx>,
86                mir: &'a Mir<'tcx>,
87                mdpe: &'a MoveDataParamEnv<'gcx, 'tcx>)
88                -> Self
89     {
90         MaybeInitializedPlaces { tcx: tcx, mir: mir, mdpe: mdpe }
91     }
92 }
93
94 impl<'a, 'gcx, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'gcx, 'tcx> {
95     fn move_data(&self) -> &MoveData<'tcx> { &self.mdpe.move_data }
96 }
97
98 /// `MaybeUninitializedPlaces` tracks all places that might be
99 /// uninitialized upon reaching a particular point in the control flow
100 /// for a function.
101 ///
102 /// For example, in code like the following, we have corresponding
103 /// dataflow information shown in the right-hand comments.
104 ///
105 /// ```rust
106 /// struct S;
107 /// fn foo(pred: bool) {                       // maybe-uninit:
108 ///                                            // {a, b, c, d}
109 ///     let a = S; let b = S; let c; let d;    // {      c, d}
110 ///
111 ///     if pred {
112 ///         drop(a);                           // {a,    c, d}
113 ///         b = S;                             // {a,    c, d}
114 ///
115 ///     } else {
116 ///         drop(b);                           // {   b, c, d}
117 ///         d = S;                             // {   b, c   }
118 ///
119 ///     }                                      // {a, b, c, d}
120 ///
121 ///     c = S;                                 // {a, b,    d}
122 /// }
123 /// ```
124 ///
125 /// To determine whether a place *must* be uninitialized at a
126 /// particular control-flow point, one can take the set-difference
127 /// between this data and the data from `MaybeInitializedPlaces` at the
128 /// corresponding control-flow point.
129 ///
130 /// Similarly, at a given `drop` statement, the set-intersection
131 /// between this data and `MaybeInitializedPlaces` yields the set of
132 /// places that would require a dynamic drop-flag at that statement.
133 pub struct MaybeUninitializedPlaces<'a, 'gcx: 'tcx, 'tcx: 'a> {
134     tcx: TyCtxt<'a, 'gcx, 'tcx>,
135     mir: &'a Mir<'tcx>,
136     mdpe: &'a MoveDataParamEnv<'gcx, 'tcx>,
137 }
138
139 impl<'a, 'gcx, 'tcx> MaybeUninitializedPlaces<'a, 'gcx, 'tcx> {
140     pub fn new(tcx: TyCtxt<'a, 'gcx, 'tcx>,
141                mir: &'a Mir<'tcx>,
142                mdpe: &'a MoveDataParamEnv<'gcx, 'tcx>)
143                -> Self
144     {
145         MaybeUninitializedPlaces { tcx: tcx, mir: mir, mdpe: mdpe }
146     }
147 }
148
149 impl<'a, 'gcx, 'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'a, 'gcx, 'tcx> {
150     fn move_data(&self) -> &MoveData<'tcx> { &self.mdpe.move_data }
151 }
152
153 /// `DefinitelyInitializedPlaces` tracks all places that are definitely
154 /// initialized upon reaching a particular point in the control flow
155 /// for a function.
156 ///
157 /// FIXME: Note that once flow-analysis is complete, this should be
158 /// the set-complement of MaybeUninitializedPlaces; thus we can get rid
159 /// of one or the other of these two. I'm inclined to get rid of
160 /// MaybeUninitializedPlaces, simply because the sets will tend to be
161 /// smaller in this analysis and thus easier for humans to process
162 /// when debugging.
163 ///
164 /// For example, in code like the following, we have corresponding
165 /// dataflow information shown in the right-hand comments.
166 ///
167 /// ```rust
168 /// struct S;
169 /// fn foo(pred: bool) {                       // definite-init:
170 ///                                            // {          }
171 ///     let a = S; let b = S; let c; let d;    // {a, b      }
172 ///
173 ///     if pred {
174 ///         drop(a);                           // {   b,     }
175 ///         b = S;                             // {   b,     }
176 ///
177 ///     } else {
178 ///         drop(b);                           // {a,        }
179 ///         d = S;                             // {a,       d}
180 ///
181 ///     }                                      // {          }
182 ///
183 ///     c = S;                                 // {       c  }
184 /// }
185 /// ```
186 ///
187 /// To determine whether a place *may* be uninitialized at a
188 /// particular control-flow point, one can take the set-complement
189 /// of this data.
190 ///
191 /// Similarly, at a given `drop` statement, the set-difference between
192 /// this data and `MaybeInitializedPlaces` yields the set of places
193 /// that would require a dynamic drop-flag at that statement.
194 pub struct DefinitelyInitializedPlaces<'a, 'gcx: 'tcx, 'tcx: 'a> {
195     tcx: TyCtxt<'a, 'gcx, 'tcx>,
196     mir: &'a Mir<'tcx>,
197     mdpe: &'a MoveDataParamEnv<'gcx, 'tcx>,
198 }
199
200 impl<'a, 'gcx, 'tcx: 'a> DefinitelyInitializedPlaces<'a, 'gcx, 'tcx> {
201     pub fn new(tcx: TyCtxt<'a, 'gcx, 'tcx>,
202                mir: &'a Mir<'tcx>,
203                mdpe: &'a MoveDataParamEnv<'gcx, 'tcx>)
204                -> Self
205     {
206         DefinitelyInitializedPlaces { tcx: tcx, mir: mir, mdpe: mdpe }
207     }
208 }
209
210 impl<'a, 'gcx, 'tcx: 'a> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, 'gcx, 'tcx> {
211     fn move_data(&self) -> &MoveData<'tcx> { &self.mdpe.move_data }
212 }
213
214 /// `EverInitializedPlaces` tracks all places that might have ever been
215 /// initialized upon reaching a particular point in the control flow
216 /// for a function, without an intervening `Storage Dead`.
217 ///
218 /// This dataflow is used to determine if an immutable local variable may
219 /// be assigned to.
220 ///
221 /// For example, in code like the following, we have corresponding
222 /// dataflow information shown in the right-hand comments.
223 ///
224 /// ```rust
225 /// struct S;
226 /// fn foo(pred: bool) {                       // ever-init:
227 ///                                            // {          }
228 ///     let a = S; let b = S; let c; let d;    // {a, b      }
229 ///
230 ///     if pred {
231 ///         drop(a);                           // {a, b,     }
232 ///         b = S;                             // {a, b,     }
233 ///
234 ///     } else {
235 ///         drop(b);                           // {a, b,      }
236 ///         d = S;                             // {a, b,    d }
237 ///
238 ///     }                                      // {a, b,    d }
239 ///
240 ///     c = S;                                 // {a, b, c, d }
241 /// }
242 /// ```
243 pub struct EverInitializedPlaces<'a, 'gcx: 'tcx, 'tcx: 'a> {
244     tcx: TyCtxt<'a, 'gcx, 'tcx>,
245     mir: &'a Mir<'tcx>,
246     mdpe: &'a MoveDataParamEnv<'gcx, 'tcx>,
247 }
248
249 impl<'a, 'gcx: 'tcx, 'tcx: 'a> EverInitializedPlaces<'a, 'gcx, 'tcx> {
250     pub fn new(tcx: TyCtxt<'a, 'gcx, 'tcx>,
251                mir: &'a Mir<'tcx>,
252                mdpe: &'a MoveDataParamEnv<'gcx, 'tcx>)
253                -> Self
254     {
255         EverInitializedPlaces { tcx: tcx, mir: mir, mdpe: mdpe }
256     }
257 }
258
259 impl<'a, 'gcx, 'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'a, 'gcx, 'tcx> {
260     fn move_data(&self) -> &MoveData<'tcx> { &self.mdpe.move_data }
261 }
262
263
264 impl<'a, 'gcx, 'tcx> MaybeInitializedPlaces<'a, 'gcx, 'tcx> {
265     fn update_bits(sets: &mut BlockSets<MovePathIndex>, path: MovePathIndex,
266                    state: DropFlagState)
267     {
268         match state {
269             DropFlagState::Absent => sets.kill(&path),
270             DropFlagState::Present => sets.gen(&path),
271         }
272     }
273 }
274
275 impl<'a, 'gcx, 'tcx> MaybeUninitializedPlaces<'a, 'gcx, 'tcx> {
276     fn update_bits(sets: &mut BlockSets<MovePathIndex>, path: MovePathIndex,
277                    state: DropFlagState)
278     {
279         match state {
280             DropFlagState::Absent => sets.gen(&path),
281             DropFlagState::Present => sets.kill(&path),
282         }
283     }
284 }
285
286 impl<'a, 'gcx, 'tcx> DefinitelyInitializedPlaces<'a, 'gcx, 'tcx> {
287     fn update_bits(sets: &mut BlockSets<MovePathIndex>, path: MovePathIndex,
288                    state: DropFlagState)
289     {
290         match state {
291             DropFlagState::Absent => sets.kill(&path),
292             DropFlagState::Present => sets.gen(&path),
293         }
294     }
295 }
296
297 impl<'a, 'gcx, 'tcx> BitDenotation for MaybeInitializedPlaces<'a, 'gcx, 'tcx> {
298     type Idx = MovePathIndex;
299     fn name() -> &'static str { "maybe_init" }
300     fn bits_per_block(&self) -> usize {
301         self.move_data().move_paths.len()
302     }
303
304     fn start_block_effect(&self, entry_set: &mut IdxSet<MovePathIndex>) {
305         drop_flag_effects_for_function_entry(
306             self.tcx, self.mir, self.mdpe,
307             |path, s| {
308                 assert!(s == DropFlagState::Present);
309                 entry_set.add(&path);
310             });
311     }
312
313     fn statement_effect(&self,
314                         sets: &mut BlockSets<MovePathIndex>,
315                         location: Location)
316     {
317         drop_flag_effects_for_location(
318             self.tcx, self.mir, self.mdpe,
319             location,
320             |path, s| Self::update_bits(sets, path, s)
321         )
322     }
323
324     fn terminator_effect(&self,
325                          sets: &mut BlockSets<MovePathIndex>,
326                          location: Location)
327     {
328         drop_flag_effects_for_location(
329             self.tcx, self.mir, self.mdpe,
330             location,
331             |path, s| Self::update_bits(sets, path, s)
332         )
333     }
334
335     fn propagate_call_return(&self,
336                              in_out: &mut IdxSet<MovePathIndex>,
337                              _call_bb: mir::BasicBlock,
338                              _dest_bb: mir::BasicBlock,
339                              dest_place: &mir::Place) {
340         // when a call returns successfully, that means we need to set
341         // the bits for that dest_place to 1 (initialized).
342         on_lookup_result_bits(self.tcx, self.mir, self.move_data(),
343                               self.move_data().rev_lookup.find(dest_place),
344                               |mpi| { in_out.add(&mpi); });
345     }
346 }
347
348 impl<'a, 'gcx, 'tcx> BitDenotation for MaybeUninitializedPlaces<'a, 'gcx, 'tcx> {
349     type Idx = MovePathIndex;
350     fn name() -> &'static str { "maybe_uninit" }
351     fn bits_per_block(&self) -> usize {
352         self.move_data().move_paths.len()
353     }
354
355     // sets on_entry bits for Arg places
356     fn start_block_effect(&self, entry_set: &mut IdxSet<MovePathIndex>) {
357         // set all bits to 1 (uninit) before gathering counterevidence
358         entry_set.set_up_to(self.bits_per_block());
359
360         drop_flag_effects_for_function_entry(
361             self.tcx, self.mir, self.mdpe,
362             |path, s| {
363                 assert!(s == DropFlagState::Present);
364                 entry_set.remove(&path);
365             });
366     }
367
368     fn statement_effect(&self,
369                         sets: &mut BlockSets<MovePathIndex>,
370                         location: Location)
371     {
372         drop_flag_effects_for_location(
373             self.tcx, self.mir, self.mdpe,
374             location,
375             |path, s| Self::update_bits(sets, path, s)
376         )
377     }
378
379     fn terminator_effect(&self,
380                          sets: &mut BlockSets<MovePathIndex>,
381                          location: Location)
382     {
383         drop_flag_effects_for_location(
384             self.tcx, self.mir, self.mdpe,
385             location,
386             |path, s| Self::update_bits(sets, path, s)
387         )
388     }
389
390     fn propagate_call_return(&self,
391                              in_out: &mut IdxSet<MovePathIndex>,
392                              _call_bb: mir::BasicBlock,
393                              _dest_bb: mir::BasicBlock,
394                              dest_place: &mir::Place) {
395         // when a call returns successfully, that means we need to set
396         // the bits for that dest_place to 0 (initialized).
397         on_lookup_result_bits(self.tcx, self.mir, self.move_data(),
398                               self.move_data().rev_lookup.find(dest_place),
399                               |mpi| { in_out.remove(&mpi); });
400     }
401 }
402
403 impl<'a, 'gcx, 'tcx> BitDenotation for DefinitelyInitializedPlaces<'a, 'gcx, 'tcx> {
404     type Idx = MovePathIndex;
405     fn name() -> &'static str { "definite_init" }
406     fn bits_per_block(&self) -> usize {
407         self.move_data().move_paths.len()
408     }
409
410     // sets on_entry bits for Arg places
411     fn start_block_effect(&self, entry_set: &mut IdxSet<MovePathIndex>) {
412         entry_set.clear();
413
414         drop_flag_effects_for_function_entry(
415             self.tcx, self.mir, self.mdpe,
416             |path, s| {
417                 assert!(s == DropFlagState::Present);
418                 entry_set.add(&path);
419             });
420     }
421
422     fn statement_effect(&self,
423                         sets: &mut BlockSets<MovePathIndex>,
424                         location: Location)
425     {
426         drop_flag_effects_for_location(
427             self.tcx, self.mir, self.mdpe,
428             location,
429             |path, s| Self::update_bits(sets, path, s)
430         )
431     }
432
433     fn terminator_effect(&self,
434                          sets: &mut BlockSets<MovePathIndex>,
435                          location: Location)
436     {
437         drop_flag_effects_for_location(
438             self.tcx, self.mir, self.mdpe,
439             location,
440             |path, s| Self::update_bits(sets, path, s)
441         )
442     }
443
444     fn propagate_call_return(&self,
445                              in_out: &mut IdxSet<MovePathIndex>,
446                              _call_bb: mir::BasicBlock,
447                              _dest_bb: mir::BasicBlock,
448                              dest_place: &mir::Place) {
449         // when a call returns successfully, that means we need to set
450         // the bits for that dest_place to 1 (initialized).
451         on_lookup_result_bits(self.tcx, self.mir, self.move_data(),
452                               self.move_data().rev_lookup.find(dest_place),
453                               |mpi| { in_out.add(&mpi); });
454     }
455 }
456
457 impl<'a, 'gcx, 'tcx> BitDenotation for EverInitializedPlaces<'a, 'gcx, 'tcx> {
458     type Idx = InitIndex;
459     fn name() -> &'static str { "ever_init" }
460     fn bits_per_block(&self) -> usize {
461         self.move_data().inits.len()
462     }
463
464     fn start_block_effect(&self, entry_set: &mut IdxSet<InitIndex>) {
465         for arg_init in 0..self.mir.arg_count {
466             entry_set.add(&InitIndex::new(arg_init));
467         }
468     }
469
470     fn statement_effect(&self,
471                         sets: &mut BlockSets<InitIndex>,
472                         location: Location) {
473         let (_, mir, move_data) = (self.tcx, self.mir, self.move_data());
474         let stmt = &mir[location.block].statements[location.statement_index];
475         let init_path_map = &move_data.init_path_map;
476         let init_loc_map = &move_data.init_loc_map;
477         let rev_lookup = &move_data.rev_lookup;
478
479         debug!("statement {:?} at loc {:?} initializes move_indexes {:?}",
480                stmt, location, &init_loc_map[location]);
481         sets.gen_all(&init_loc_map[location]);
482
483         match stmt.kind {
484             mir::StatementKind::StorageDead(local) |
485             mir::StatementKind::StorageLive(local) => {
486                 // End inits for StorageDead and StorageLive, so that an immutable
487                 // variable can be reinitialized on the next iteration of the loop.
488                 //
489                 // FIXME(#46525): We *need* to do this for StorageLive as well as
490                 // StorageDead, because lifetimes of match bindings with guards are
491                 // weird - i.e. this code
492                 //
493                 // ```
494                 //     fn main() {
495                 //         match 0 {
496                 //             a | a
497                 //             if { println!("a={}", a); false } => {}
498                 //             _ => {}
499                 //         }
500                 //     }
501                 // ```
502                 //
503                 // runs the guard twice, using the same binding for `a`, and only
504                 // storagedeads after everything ends, so if we don't regard the
505                 // storagelive as killing storage, we would have a multiple assignment
506                 // to immutable data error.
507                 if let LookupResult::Exact(mpi) = rev_lookup.find(&mir::Place::Local(local)) {
508                     debug!("stmt {:?} at loc {:?} clears the ever initialized status of {:?}",
509                            stmt, location, &init_path_map[mpi]);
510                     sets.kill_all(&init_path_map[mpi]);
511                 }
512             }
513             _ => {}
514         }
515     }
516
517     fn terminator_effect(&self,
518                          sets: &mut BlockSets<InitIndex>,
519                          location: Location)
520     {
521         let (mir, move_data) = (self.mir, self.move_data());
522         let term = mir[location.block].terminator();
523         let init_loc_map = &move_data.init_loc_map;
524         debug!("terminator {:?} at loc {:?} initializes move_indexes {:?}",
525                term, location, &init_loc_map[location]);
526         sets.gen_all(
527             init_loc_map[location].iter().filter(|init_index| {
528                 move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly
529             })
530         );
531     }
532
533     fn propagate_call_return(&self,
534                              in_out: &mut IdxSet<InitIndex>,
535                              call_bb: mir::BasicBlock,
536                              _dest_bb: mir::BasicBlock,
537                              _dest_place: &mir::Place) {
538         let move_data = self.move_data();
539         let bits_per_block = self.bits_per_block();
540         let init_loc_map = &move_data.init_loc_map;
541
542         let call_loc = Location {
543             block: call_bb,
544             statement_index: self.mir[call_bb].statements.len(),
545         };
546         for init_index in &init_loc_map[call_loc] {
547             assert!(init_index.index() < bits_per_block);
548             in_out.add(init_index);
549         }
550     }
551 }
552
553 impl<'a, 'gcx, 'tcx> BitwiseOperator for MaybeInitializedPlaces<'a, 'gcx, 'tcx> {
554     #[inline]
555     fn join(&self, pred1: Word, pred2: Word) -> Word {
556         pred1 | pred2 // "maybe" means we union effects of both preds
557     }
558 }
559
560 impl<'a, 'gcx, 'tcx> BitwiseOperator for MaybeUninitializedPlaces<'a, 'gcx, 'tcx> {
561     #[inline]
562     fn join(&self, pred1: Word, pred2: Word) -> Word {
563         pred1 | pred2 // "maybe" means we union effects of both preds
564     }
565 }
566
567 impl<'a, 'gcx, 'tcx> BitwiseOperator for DefinitelyInitializedPlaces<'a, 'gcx, 'tcx> {
568     #[inline]
569     fn join(&self, pred1: Word, pred2: Word) -> Word {
570         pred1 & pred2 // "definitely" means we intersect effects of both preds
571     }
572 }
573
574 impl<'a, 'gcx, 'tcx> BitwiseOperator for EverInitializedPlaces<'a, 'gcx, 'tcx> {
575     #[inline]
576     fn join(&self, pred1: Word, pred2: Word) -> Word {
577         pred1 | pred2 // inits from both preds are in scope
578     }
579 }
580
581 // The way that dataflow fixed point iteration works, you want to
582 // start at bottom and work your way to a fixed point. Control-flow
583 // merges will apply the `join` operator to each block entry's current
584 // state (which starts at that bottom value).
585 //
586 // This means, for propagation across the graph, that you either want
587 // to start at all-zeroes and then use Union as your merge when
588 // propagating, or you start at all-ones and then use Intersect as
589 // your merge when propagating.
590
591 impl<'a, 'gcx, 'tcx> InitialFlow for MaybeInitializedPlaces<'a, 'gcx, 'tcx> {
592     #[inline]
593     fn bottom_value() -> bool {
594         false // bottom = uninitialized
595     }
596 }
597
598 impl<'a, 'gcx, 'tcx> InitialFlow for MaybeUninitializedPlaces<'a, 'gcx, 'tcx> {
599     #[inline]
600     fn bottom_value() -> bool {
601         false // bottom = initialized (start_block_effect counters this at outset)
602     }
603 }
604
605 impl<'a, 'gcx, 'tcx> InitialFlow for DefinitelyInitializedPlaces<'a, 'gcx, 'tcx> {
606     #[inline]
607     fn bottom_value() -> bool {
608         true // bottom = initialized (start_block_effect counters this at outset)
609     }
610 }
611
612 impl<'a, 'gcx, 'tcx> InitialFlow for EverInitializedPlaces<'a, 'gcx, 'tcx> {
613     #[inline]
614     fn bottom_value() -> bool {
615         false // bottom = no initialized variables by default
616     }
617 }