1
- //! Global value numbering.
1
+ //! Value numbering.
2
2
//!
3
3
//! MIR may contain repeated and/or redundant computations. The objective of this pass is to detect
4
4
//! such redundancies and re-use the already-computed result when possible.
8
8
//!
9
9
//! We traverse all assignments `x = rvalue` and operands.
10
10
//!
11
- //! For each SSA one , we compute a symbolic representation of values that are assigned to SSA
11
+ //! For each assignment , we compute a symbolic representation of values that are assigned to SSA
12
12
//! locals. This symbolic representation is defined by the `Value` enum. Each produced instance of
13
13
//! `Value` is interned as a `VnIndex`, which allows us to cheaply compute identical values.
14
14
//!
15
- //! For each non-SSA
16
- //! one, we compute the `VnIndex` of the rvalue. If this `VnIndex` is associated to a constant, we
17
- //! replace the rvalue/operand by that constant. Otherwise, if there is an SSA local `y`
18
- //! associated to this `VnIndex`, and if its definition location strictly dominates the assignment
19
- //! to `x`, we replace the assignment by `x = y`.
15
+ //! If the local is SSA, we append it into the mapping `rev_locals_ssa[value]` for later reuse.
16
+ //! That mapping accumulates the knowledge across basic blocks.
17
+ //!
18
+ //! If the local is non-SSA, we remove it from `rev_locals_non_ssa[old_value]` and append it to
19
+ //! `rev_locals_non_ssa[new_value]`. That mapping is cleared at the beginning of each basic block,
20
+ //! to ensure we do not carry information across blocks.
21
+ //!
22
+ //! If the computed `VnIndex` is associated to a constant, we replace the rvalue/operand by that
23
+ //! constant. Otherwise, if there is a local `y` associated to this `VnIndex`:
24
+ //! - if `y` is SSA and its definition location strictly dominates the assignment to `x`, we
25
+ //! replace the assignment by `x = y`;
26
+ //! - if `y` is not SSA, then the assignment happens earlier in the same block, so we replace the
27
+ //! assignment by `x = y`.
20
28
//!
21
29
//! By opportunity, this pass simplifies some `Rvalue`s based on the accumulated knowledge.
22
30
//!
@@ -97,7 +105,7 @@ use rustc_const_eval::interpret::{
97
105
ImmTy , Immediate , InterpCx , MemPlaceMeta , MemoryKind , OpTy , Projectable , Scalar ,
98
106
intern_const_alloc_for_constprop,
99
107
} ;
100
- use rustc_data_structures:: fx:: FxHasher ;
108
+ use rustc_data_structures:: fx:: { FxHashMap , FxHasher } ;
101
109
use rustc_data_structures:: graph:: dominators:: Dominators ;
102
110
use rustc_hir:: def:: DefKind ;
103
111
use rustc_index:: bit_set:: DenseBitSet ;
@@ -358,8 +366,11 @@ struct VnState<'body, 'a, 'tcx> {
358
366
/// Value stored in each local.
359
367
locals : IndexVec < Local , Option < VnIndex > > ,
360
368
/// Locals that are assigned that value.
361
- // This vector does not hold all the values of `VnIndex` that we create.
362
- rev_locals : IndexVec < VnIndex , SmallVec < [ Local ; 1 ] > > ,
369
+ // This vector holds the locals that are SSA.
370
+ rev_locals_ssa : IndexVec < VnIndex , SmallVec < [ Local ; 1 ] > > ,
371
+ // This map holds the locals that are not SSA. This map is cleared at the end of each block.
372
+ // Therefore, we do not need a location, the local always appears before the current location.
373
+ rev_locals_non_ssa : FxHashMap < VnIndex , SmallVec < [ Local ; 1 ] > > ,
363
374
values : ValueSet < ' a , ' tcx > ,
364
375
/// Values evaluated as constants if possible.
365
376
evaluated : IndexVec < VnIndex , Option < OpTy < ' tcx > > > ,
@@ -394,7 +405,8 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
394
405
local_decls,
395
406
is_coroutine : body. coroutine . is_some ( ) ,
396
407
locals : IndexVec :: from_elem ( None , local_decls) ,
397
- rev_locals : IndexVec :: with_capacity ( num_values) ,
408
+ rev_locals_ssa : IndexVec :: with_capacity ( num_values) ,
409
+ rev_locals_non_ssa : FxHashMap :: default ( ) ,
398
410
values : ValueSet :: new ( num_values) ,
399
411
evaluated : IndexVec :: with_capacity ( num_values) ,
400
412
derefs : Vec :: new ( ) ,
@@ -413,11 +425,11 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
413
425
fn insert ( & mut self , ty : Ty < ' tcx > , value : Value < ' a , ' tcx > ) -> VnIndex {
414
426
let ( index, new) = self . values . insert ( ty, value) ;
415
427
if new {
416
- // Grow `evaluated` and `rev_locals ` here to amortize the allocations.
428
+ // Grow `evaluated` and `rev_locals_ssa ` here to amortize the allocations.
417
429
let evaluated = self . eval_to_const ( index) ;
418
430
let _index = self . evaluated . push ( evaluated) ;
419
431
debug_assert_eq ! ( index, _index) ;
420
- let _index = self . rev_locals . push ( SmallVec :: new ( ) ) ;
432
+ let _index = self . rev_locals_ssa . push ( Default :: default ( ) ) ;
421
433
debug_assert_eq ! ( index, _index) ;
422
434
}
423
435
index
@@ -430,7 +442,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
430
442
let index = self . values . insert_unique ( ty, Value :: Opaque ) ;
431
443
let _index = self . evaluated . push ( None ) ;
432
444
debug_assert_eq ! ( index, _index) ;
433
- let _index = self . rev_locals . push ( SmallVec :: new ( ) ) ;
445
+ let _index = self . rev_locals_ssa . push ( SmallVec :: new ( ) ) ;
434
446
debug_assert_eq ! ( index, _index) ;
435
447
index
436
448
}
@@ -448,17 +460,19 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
448
460
449
461
let mut projection = place. projection . iter ( ) ;
450
462
let base = if place. is_indirect_first_projection ( ) {
451
- let base = self . locals [ place. local ] ? ;
463
+ let base = self . local ( place. local ) ;
452
464
// Skip the initial `Deref`.
453
465
projection. next ( ) ;
454
466
AddressBase :: Deref ( base)
455
467
} else {
456
468
AddressBase :: Local ( place. local )
457
469
} ;
470
+ let arena = self . arena ;
471
+
458
472
// Do not try evaluating inside `Index`, this has been done by `simplify_place_projection`.
459
473
let projection =
460
- projection. map ( |proj| proj. try_map ( |index| self . locals [ index] , |ty| ty) . ok_or ( ( ) ) ) ;
461
- let projection = self . arena . try_alloc_from_iter ( projection) . ok ( ) ?;
474
+ projection. map ( |proj| proj. try_map ( |index| Some ( self . local ( index) ) , |ty| ty) . ok_or ( ( ) ) ) ;
475
+ let projection = arena. try_alloc_from_iter ( projection) . ok ( ) ?;
462
476
463
477
let index = self . values . insert_unique ( ty, |provenance| Value :: Address {
464
478
base,
@@ -469,7 +483,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
469
483
let evaluated = self . eval_to_const ( index) ;
470
484
let _index = self . evaluated . push ( evaluated) ;
471
485
debug_assert_eq ! ( index, _index) ;
472
- let _index = self . rev_locals . push ( SmallVec :: new ( ) ) ;
486
+ let _index = self . rev_locals_ssa . push ( SmallVec :: new ( ) ) ;
473
487
debug_assert_eq ! ( index, _index) ;
474
488
475
489
Some ( index)
@@ -494,7 +508,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
494
508
let evaluated = self . eval_to_const ( index) ;
495
509
let _index = self . evaluated . push ( evaluated) ;
496
510
debug_assert_eq ! ( index, _index) ;
497
- let _index = self . rev_locals . push ( SmallVec :: new ( ) ) ;
511
+ let _index = self . rev_locals_ssa . push ( SmallVec :: new ( ) ) ;
498
512
debug_assert_eq ! ( index, _index) ;
499
513
}
500
514
index
@@ -510,12 +524,49 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
510
524
self . values . ty ( index)
511
525
}
512
526
513
- /// Record that `local` is assigned `value`. `local` must be SSA.
527
+ /// Record that `local` is assigned `value`.
514
528
#[ instrument( level = "trace" , skip( self ) ) ]
515
529
fn assign ( & mut self , local : Local , value : VnIndex ) {
516
- debug_assert ! ( self . ssa. is_ssa( local) ) ;
517
530
self . locals [ local] = Some ( value) ;
518
- self . rev_locals [ value] . push ( local) ;
531
+ if self . ssa . is_ssa ( local) {
532
+ self . rev_locals_ssa [ value] . push ( local) ;
533
+ } else {
534
+ self . rev_locals_non_ssa . entry ( value) . or_default ( ) . push ( local) ;
535
+ }
536
+ }
537
+
538
+ /// Return the value assigned to a local, or assign an opaque value and return it.
539
+ #[ instrument( level = "trace" , skip( self ) , ret) ]
540
+ fn local ( & mut self , local : Local ) -> VnIndex {
541
+ if let Some ( value) = self . locals [ local] {
542
+ return value;
543
+ }
544
+ let value = self . new_opaque ( self . local_decls [ local] . ty ) ;
545
+ self . locals [ local] = Some ( value) ;
546
+ self . rev_locals_non_ssa . entry ( value) . or_default ( ) . push ( local) ;
547
+ value
548
+ }
549
+
550
+ #[ instrument( level = "trace" , skip( self ) ) ]
551
+ fn discard_place ( & mut self , place : Place < ' tcx > ) {
552
+ let discard_local = |this : & mut Self , local| {
553
+ if this. ssa . is_ssa ( local) {
554
+ return ;
555
+ }
556
+ if let Some ( value) = this. locals [ local] . take ( ) {
557
+ this. rev_locals_non_ssa . entry ( value) . or_default ( ) . retain ( |l| * l != local) ;
558
+ }
559
+ } ;
560
+ if place. is_indirect_first_projection ( ) {
561
+ // Non-local mutation maybe invalidate deref.
562
+ self . invalidate_derefs ( ) ;
563
+ // Remove stored value from borrowed locals.
564
+ for local in self . ssa . borrowed_locals ( ) . iter ( ) {
565
+ discard_local ( self , local) ;
566
+ }
567
+ } else {
568
+ discard_local ( self , place. local ) ;
569
+ }
519
570
}
520
571
521
572
fn insert_bool ( & mut self , flag : bool ) -> VnIndex {
@@ -759,7 +810,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
759
810
let ( mut place_ty, mut value) = match base {
760
811
// The base is a local, so we take the local's value and project from it.
761
812
AddressBase :: Local ( local) => {
762
- let local = self . locals [ local] ? ;
813
+ let local = self . local ( local ) ;
763
814
let place_ty = PlaceTy :: from_ty ( self . ty ( local) ) ;
764
815
( place_ty, local)
765
816
}
@@ -865,7 +916,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
865
916
// If the projection is indirect, we treat the local as a value, so can replace it with
866
917
// another local.
867
918
if place. is_indirect_first_projection ( )
868
- && let Some ( base) = self . locals [ place. local ]
919
+ && let base = self . local ( place. local )
869
920
&& let Some ( new_local) = self . try_as_local ( base, location)
870
921
&& place. local != new_local
871
922
{
@@ -877,9 +928,8 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
877
928
878
929
for i in 0 ..projection. len ( ) {
879
930
let elem = projection[ i] ;
880
- if let ProjectionElem :: Index ( idx_local) = elem
881
- && let Some ( idx) = self . locals [ idx_local]
882
- {
931
+ if let ProjectionElem :: Index ( idx_local) = elem {
932
+ let idx = self . local ( idx_local) ;
883
933
if let Some ( offset) = self . evaluated [ idx] . as_ref ( )
884
934
&& let Some ( offset) = self . ecx . read_target_usize ( offset) . discard_err ( )
885
935
&& let Some ( min_length) = offset. checked_add ( 1 )
@@ -915,7 +965,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
915
965
let mut place_ref = place. as_ref ( ) ;
916
966
917
967
// Invariant: `value` holds the value up-to the `index`th projection excluded.
918
- let Some ( mut value) = self . locals [ place. local ] else { return Err ( place_ref ) } ;
968
+ let mut value = self . local ( place. local ) ;
919
969
// Invariant: `value` has type `place_ty`, with optional downcast variant if needed.
920
970
let mut place_ty = PlaceTy :: from_ty ( self . local_decls [ place. local ] . ty ) ;
921
971
for ( index, proj) in place. projection . iter ( ) . enumerate ( ) {
@@ -926,7 +976,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
926
976
place_ref = PlaceRef { local, projection : & place. projection [ index..] } ;
927
977
}
928
978
929
- let Some ( proj) = proj. try_map ( |value| self . locals [ value] , |ty| ty) else {
979
+ let Some ( proj) = proj. try_map ( |value| Some ( self . local ( value) ) , |ty| ty) else {
930
980
return Err ( place_ref) ;
931
981
} ;
932
982
let Some ( ty_and_value) = self . project ( place_ty, value, proj) else {
@@ -1809,11 +1859,17 @@ impl<'tcx> VnState<'_, '_, 'tcx> {
1809
1859
/// If there is a local which is assigned `index`, and its assignment strictly dominates `loc`,
1810
1860
/// return it. If you used this local, add it to `reused_locals` to remove storage statements.
1811
1861
fn try_as_local ( & mut self , index : VnIndex , loc : Location ) -> Option < Local > {
1812
- let other = self . rev_locals . get ( index) ?;
1813
- other
1814
- . iter ( )
1815
- . find ( |& & other| self . ssa . assignment_dominates ( & self . dominators , other, loc) )
1816
- . copied ( )
1862
+ if let Some ( ssa) = self . rev_locals_ssa . get ( index)
1863
+ && let Some ( other) = ssa
1864
+ . iter ( )
1865
+ . find ( |& & other| self . ssa . assignment_dominates ( & self . dominators , other, loc) )
1866
+ {
1867
+ Some ( * other)
1868
+ } else if let Some ( non_ssa) = self . rev_locals_non_ssa . get ( & index) {
1869
+ non_ssa. first ( ) . copied ( )
1870
+ } else {
1871
+ None
1872
+ }
1817
1873
}
1818
1874
}
1819
1875
@@ -1822,11 +1878,20 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> {
1822
1878
self . tcx
1823
1879
}
1824
1880
1881
+ fn visit_basic_block_data ( & mut self , block : BasicBlock , bbdata : & mut BasicBlockData < ' tcx > ) {
1882
+ self . rev_locals_non_ssa . clear ( ) ;
1883
+ for local in self . locals . indices ( ) {
1884
+ if !self . ssa . is_ssa ( local) {
1885
+ self . locals [ local] = None ;
1886
+ }
1887
+ }
1888
+ self . super_basic_block_data ( block, bbdata) ;
1889
+ }
1890
+
1825
1891
fn visit_place ( & mut self , place : & mut Place < ' tcx > , context : PlaceContext , location : Location ) {
1826
1892
self . simplify_place_projection ( place, location) ;
1827
- if context. is_mutating_use ( ) && place. is_indirect ( ) {
1828
- // Non-local mutation maybe invalidate deref.
1829
- self . invalidate_derefs ( ) ;
1893
+ if context. is_mutating_use ( ) {
1894
+ self . discard_place ( * place) ;
1830
1895
}
1831
1896
self . super_place ( place, context, location) ;
1832
1897
}
@@ -1865,13 +1930,9 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> {
1865
1930
}
1866
1931
}
1867
1932
1868
- if lhs. is_indirect ( ) {
1869
- // Non-local mutation maybe invalidate deref.
1870
- self . invalidate_derefs ( ) ;
1871
- }
1933
+ self . discard_place ( * lhs) ;
1872
1934
1873
1935
if let Some ( local) = lhs. as_local ( )
1874
- && self . ssa . is_ssa ( local)
1875
1936
&& let rvalue_ty = rvalue. ty ( self . local_decls , self . tcx )
1876
1937
// FIXME(#112651) `rvalue` may have a subtype to `local`. We can only mark
1877
1938
// `local` as reusable if we have an exact type match.
@@ -1883,14 +1944,13 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> {
1883
1944
}
1884
1945
1885
1946
fn visit_terminator ( & mut self , terminator : & mut Terminator < ' tcx > , location : Location ) {
1886
- if let Terminator { kind : TerminatorKind :: Call { destination, .. } , .. } = terminator {
1887
- if let Some ( local) = destination. as_local ( )
1888
- && self . ssa . is_ssa ( local)
1889
- {
1890
- let ty = self . local_decls [ local] . ty ;
1891
- let opaque = self . new_opaque ( ty) ;
1892
- self . assign ( local, opaque) ;
1893
- }
1947
+ self . super_terminator ( terminator, location) ;
1948
+ if let Terminator { kind : TerminatorKind :: Call { destination, .. } , .. } = terminator
1949
+ && let Some ( local) = destination. as_local ( )
1950
+ {
1951
+ let ty = self . local_decls [ local] . ty ;
1952
+ let opaque = self . new_opaque ( ty) ;
1953
+ self . assign ( local, opaque) ;
1894
1954
}
1895
1955
// Function calls and ASM may invalidate (nested) derefs. We must handle them carefully.
1896
1956
// Currently, only preserving derefs for trivial terminators like SwitchInt and Goto.
@@ -1901,7 +1961,6 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> {
1901
1961
if !safe_to_preserve_derefs {
1902
1962
self . invalidate_derefs ( ) ;
1903
1963
}
1904
- self . super_terminator ( terminator, location) ;
1905
1964
}
1906
1965
}
1907
1966
0 commit comments