@@ -106,15 +106,7 @@ impl Context {
106
106
107
107
let instructions_len = block. instructions ( ) . len ( ) ;
108
108
109
- // We can track IncrementRc instructions per block to determine whether they are useless.
110
- // IncrementRc and DecrementRc instructions are normally side effectual instructions, but we remove
111
- // them if their value is not used anywhere in the function. However, even when their value is used, their existence
112
- // is pointless logic if there is no array set between the increment and the decrement of the reference counter.
113
- // We track per block whether an IncrementRc instruction has a paired DecrementRc instruction
114
- // with the same value but no array set in between.
115
- // If we see an inc/dec RC pair within a block we can safely remove both instructions.
116
- let mut inc_rcs: HashMap < Type , Vec < RcInstruction > > = HashMap :: default ( ) ;
117
- let mut inc_rcs_to_remove = HashSet :: default ( ) ;
109
+ let mut rc_tracker = RcTracker :: default ( ) ;
118
110
119
111
// Indexes of instructions that might be out of bounds.
120
112
// We'll remove those, but before that we'll insert bounds checks for them.
@@ -143,17 +135,11 @@ impl Context {
143
135
}
144
136
}
145
137
146
- self . track_inc_rcs_to_remove (
147
- * instruction_id,
148
- function,
149
- & mut inc_rcs,
150
- & mut inc_rcs_to_remove,
151
- ) ;
138
+ rc_tracker. track_inc_rcs_to_remove ( * instruction_id, function) ;
152
139
}
153
140
154
- for id in inc_rcs_to_remove {
155
- self . instructions_to_remove . insert ( id) ;
156
- }
141
+ self . instructions_to_remove . extend ( rc_tracker. get_non_mutated_arrays ( ) ) ;
142
+ self . instructions_to_remove . extend ( rc_tracker. rc_pairs_to_remove ) ;
157
143
158
144
// If there are some instructions that might trigger an out of bounds error,
159
145
// first add constrain checks. Then run the DIE pass again, which will remove those
@@ -568,10 +554,109 @@ fn apply_side_effects(
568
554
( lhs, rhs)
569
555
}
570
556
557
+ #[ derive( Default ) ]
558
+ struct RcTracker {
559
+ // We can track IncrementRc instructions per block to determine whether they are useless.
560
+ // IncrementRc and DecrementRc instructions are normally side effectual instructions, but we remove
561
+ // them if their value is not used anywhere in the function. However, even when their value is used, their existence
562
+ // is pointless logic if there is no array set between the increment and the decrement of the reference counter.
563
+ // We track per block whether an IncrementRc instruction has a paired DecrementRc instruction
564
+ // with the same value but no array set in between.
565
+ // If we see an inc/dec RC pair within a block we can safely remove both instructions.
566
+ rcs_with_possible_pairs : HashMap < Type , Vec < RcInstruction > > ,
567
+ rc_pairs_to_remove : HashSet < InstructionId > ,
568
+ // We also separately track all IncrementRc instructions and all arrays which have been mutably borrowed.
569
+ // If an array has not been mutably borrowed we can then safely remove all IncrementRc instructions on that array.
570
+ inc_rcs : HashMap < ValueId , HashSet < InstructionId > > ,
571
+ mut_borrowed_arrays : HashSet < ValueId > ,
572
+ // The SSA often creates patterns where after simplifications we end up with repeat
573
+ // IncrementRc instructions on the same value. We track whether the previous instruction was an IncrementRc,
574
+ // and if the current instruction is also an IncrementRc on the same value we remove the current instruction.
575
+ // `None` if the previous instruction was anything other than an IncrementRc
576
+ previous_inc_rc : Option < ValueId > ,
577
+ }
578
+
579
+ impl RcTracker {
580
+ fn track_inc_rcs_to_remove ( & mut self , instruction_id : InstructionId , function : & Function ) {
581
+ let instruction = & function. dfg [ instruction_id] ;
582
+
583
+ if let Instruction :: IncrementRc { value } = instruction {
584
+ if let Some ( previous_value) = self . previous_inc_rc {
585
+ if previous_value == * value {
586
+ self . rc_pairs_to_remove . insert ( instruction_id) ;
587
+ }
588
+ }
589
+ self . previous_inc_rc = Some ( * value) ;
590
+ } else {
591
+ self . previous_inc_rc = None ;
592
+ }
593
+
594
+ // DIE loops over a block in reverse order, so we insert an RC instruction for possible removal
595
+ // when we see a DecrementRc and check whether it was possibly mutated when we see an IncrementRc.
596
+ match instruction {
597
+ Instruction :: IncrementRc { value } => {
598
+ if let Some ( inc_rc) =
599
+ pop_rc_for ( * value, function, & mut self . rcs_with_possible_pairs )
600
+ {
601
+ if !inc_rc. possibly_mutated {
602
+ self . rc_pairs_to_remove . insert ( inc_rc. id ) ;
603
+ self . rc_pairs_to_remove . insert ( instruction_id) ;
604
+ }
605
+ }
606
+
607
+ self . inc_rcs . entry ( * value) . or_default ( ) . insert ( instruction_id) ;
608
+ }
609
+ Instruction :: DecrementRc { value } => {
610
+ let typ = function. dfg . type_of_value ( * value) ;
611
+
612
+ // We assume arrays aren't mutated until we find an array_set
613
+ let dec_rc =
614
+ RcInstruction { id : instruction_id, array : * value, possibly_mutated : false } ;
615
+ self . rcs_with_possible_pairs . entry ( typ) . or_default ( ) . push ( dec_rc) ;
616
+ }
617
+ Instruction :: ArraySet { array, .. } => {
618
+ let typ = function. dfg . type_of_value ( * array) ;
619
+ if let Some ( dec_rcs) = self . rcs_with_possible_pairs . get_mut ( & typ) {
620
+ for dec_rc in dec_rcs {
621
+ dec_rc. possibly_mutated = true ;
622
+ }
623
+ }
624
+
625
+ self . mut_borrowed_arrays . insert ( * array) ;
626
+ }
627
+ Instruction :: Store { value, .. } => {
628
+ // We are very conservative and say that any store of an array value means it has the potential
629
+ // to be mutated. This is done due to the tracking of mutable borrows still being per block.
630
+ let typ = function. dfg . type_of_value ( * value) ;
631
+ if matches ! ( & typ, Type :: Array ( ..) | Type :: Slice ( ..) ) {
632
+ self . mut_borrowed_arrays . insert ( * value) ;
633
+ }
634
+ }
635
+ _ => { }
636
+ }
637
+ }
638
+
639
+ fn get_non_mutated_arrays ( & self ) -> HashSet < InstructionId > {
640
+ self . inc_rcs
641
+ . keys ( )
642
+ . filter_map ( |value| {
643
+ if !self . mut_borrowed_arrays . contains ( value) {
644
+ Some ( & self . inc_rcs [ value] )
645
+ } else {
646
+ None
647
+ }
648
+ } )
649
+ . flatten ( )
650
+ . copied ( )
651
+ . collect ( )
652
+ }
653
+ }
571
654
#[ cfg( test) ]
572
655
mod test {
573
656
use std:: sync:: Arc ;
574
657
658
+ use im:: vector;
659
+
575
660
use crate :: ssa:: {
576
661
function_builder:: FunctionBuilder ,
577
662
ir:: {
@@ -779,4 +864,160 @@ mod test {
779
864
780
865
assert_eq ! ( main. dfg[ main. entry_block( ) ] . instructions( ) . len( ) , 3 ) ;
781
866
}
867
+
868
+ #[ test]
869
+ fn keep_inc_rc_on_borrowed_array_store ( ) {
870
+ // acir(inline) fn main f0 {
871
+ // b0():
872
+ // v2 = allocate
873
+ // inc_rc [u32 0, u32 0]
874
+ // store [u32 0, u32 0] at v2
875
+ // inc_rc [u32 0, u32 0]
876
+ // jmp b1()
877
+ // b1():
878
+ // v3 = load v2
879
+ // v5 = array_set v3, index u32 0, value u32 1
880
+ // return v5
881
+ // }
882
+ let main_id = Id :: test_new ( 0 ) ;
883
+
884
+ // Compiling main
885
+ let mut builder = FunctionBuilder :: new ( "main" . into ( ) , main_id) ;
886
+ let zero = builder. numeric_constant ( 0u128 , Type :: unsigned ( 32 ) ) ;
887
+ let array_type = Type :: Array ( Arc :: new ( vec ! [ Type :: unsigned( 32 ) ] ) , 2 ) ;
888
+ let array = builder. array_constant ( vector ! [ zero, zero] , array_type. clone ( ) ) ;
889
+ let v2 = builder. insert_allocate ( array_type. clone ( ) ) ;
890
+ builder. increment_array_reference_count ( array) ;
891
+ builder. insert_store ( v2, array) ;
892
+ builder. increment_array_reference_count ( array) ;
893
+
894
+ let b1 = builder. insert_block ( ) ;
895
+ builder. terminate_with_jmp ( b1, vec ! [ ] ) ;
896
+ builder. switch_to_block ( b1) ;
897
+
898
+ let v3 = builder. insert_load ( v2, array_type) ;
899
+ let one = builder. numeric_constant ( 1u128 , Type :: unsigned ( 32 ) ) ;
900
+ let v5 = builder. insert_array_set ( v3, zero, one) ;
901
+ builder. terminate_with_return ( vec ! [ v5] ) ;
902
+
903
+ let ssa = builder. finish ( ) ;
904
+ let main = ssa. main ( ) ;
905
+
906
+ // The instruction count never includes the terminator instruction
907
+ assert_eq ! ( main. dfg[ main. entry_block( ) ] . instructions( ) . len( ) , 4 ) ;
908
+ assert_eq ! ( main. dfg[ b1] . instructions( ) . len( ) , 2 ) ;
909
+
910
+ // We expect the output to be unchanged
911
+ let ssa = ssa. dead_instruction_elimination ( ) ;
912
+ let main = ssa. main ( ) ;
913
+
914
+ assert_eq ! ( main. dfg[ main. entry_block( ) ] . instructions( ) . len( ) , 4 ) ;
915
+ assert_eq ! ( main. dfg[ b1] . instructions( ) . len( ) , 2 ) ;
916
+ }
917
+
918
+ #[ test]
919
+ fn keep_inc_rc_on_borrowed_array_set ( ) {
920
+ // acir(inline) fn main f0 {
921
+ // b0(v0: [u32; 2]):
922
+ // inc_rc v0
923
+ // v3 = array_set v0, index u32 0, value u32 1
924
+ // inc_rc v0
925
+ // inc_rc v0
926
+ // inc_rc v0
927
+ // v4 = array_get v3, index u32 1
928
+ // return v4
929
+ // }
930
+ let main_id = Id :: test_new ( 0 ) ;
931
+
932
+ // Compiling main
933
+ let mut builder = FunctionBuilder :: new ( "main" . into ( ) , main_id) ;
934
+ let array_type = Type :: Array ( Arc :: new ( vec ! [ Type :: unsigned( 32 ) ] ) , 2 ) ;
935
+ let v0 = builder. add_parameter ( array_type. clone ( ) ) ;
936
+ builder. increment_array_reference_count ( v0) ;
937
+ let zero = builder. numeric_constant ( 0u128 , Type :: unsigned ( 32 ) ) ;
938
+ let one = builder. numeric_constant ( 1u128 , Type :: unsigned ( 32 ) ) ;
939
+ let v3 = builder. insert_array_set ( v0, zero, one) ;
940
+ builder. increment_array_reference_count ( v0) ;
941
+ builder. increment_array_reference_count ( v0) ;
942
+ builder. increment_array_reference_count ( v0) ;
943
+
944
+ let v4 = builder. insert_array_get ( v3, one, Type :: unsigned ( 32 ) ) ;
945
+
946
+ builder. terminate_with_return ( vec ! [ v4] ) ;
947
+
948
+ let ssa = builder. finish ( ) ;
949
+ let main = ssa. main ( ) ;
950
+
951
+ // The instruction count never includes the terminator instruction
952
+ assert_eq ! ( main. dfg[ main. entry_block( ) ] . instructions( ) . len( ) , 6 ) ;
953
+
954
+ // We expect the output to be unchanged
955
+ // Expected output:
956
+ //
957
+ // acir(inline) fn main f0 {
958
+ // b0(v0: [u32; 2]):
959
+ // inc_rc v0
960
+ // v3 = array_set v0, index u32 0, value u32 1
961
+ // inc_rc v0
962
+ // v4 = array_get v3, index u32 1
963
+ // return v4
964
+ // }
965
+ let ssa = ssa. dead_instruction_elimination ( ) ;
966
+ let main = ssa. main ( ) ;
967
+
968
+ let instructions = main. dfg [ main. entry_block ( ) ] . instructions ( ) ;
969
+ // We expect only the repeated inc_rc instructions to be collapsed into a single inc_rc.
970
+ assert_eq ! ( instructions. len( ) , 4 ) ;
971
+
972
+ assert ! ( matches!( & main. dfg[ instructions[ 0 ] ] , Instruction :: IncrementRc { .. } ) ) ;
973
+ assert ! ( matches!( & main. dfg[ instructions[ 1 ] ] , Instruction :: ArraySet { .. } ) ) ;
974
+ assert ! ( matches!( & main. dfg[ instructions[ 2 ] ] , Instruction :: IncrementRc { .. } ) ) ;
975
+ assert ! ( matches!( & main. dfg[ instructions[ 3 ] ] , Instruction :: ArrayGet { .. } ) ) ;
976
+ }
977
+
978
+ #[ test]
979
+ fn remove_inc_rcs_that_are_never_mutably_borrowed ( ) {
980
+ // acir(inline) fn main f0 {
981
+ // b0(v0: [Field; 2]):
982
+ // inc_rc v0
983
+ // inc_rc v0
984
+ // inc_rc v0
985
+ // v2 = array_get v0, index u32 0
986
+ // inc_rc v0
987
+ // return v2
988
+ // }
989
+ let main_id = Id :: test_new ( 0 ) ;
990
+
991
+ // Compiling main
992
+ let mut builder = FunctionBuilder :: new ( "main" . into ( ) , main_id) ;
993
+ let v0 = builder. add_parameter ( Type :: Array ( Arc :: new ( vec ! [ Type :: field( ) ] ) , 2 ) ) ;
994
+ builder. increment_array_reference_count ( v0) ;
995
+ builder. increment_array_reference_count ( v0) ;
996
+ builder. increment_array_reference_count ( v0) ;
997
+
998
+ let zero = builder. numeric_constant ( 0u128 , Type :: unsigned ( 32 ) ) ;
999
+ let v2 = builder. insert_array_get ( v0, zero, Type :: field ( ) ) ;
1000
+ builder. increment_array_reference_count ( v0) ;
1001
+ builder. terminate_with_return ( vec ! [ v2] ) ;
1002
+
1003
+ let ssa = builder. finish ( ) ;
1004
+ let main = ssa. main ( ) ;
1005
+
1006
+ // The instruction count never includes the terminator instruction
1007
+ assert_eq ! ( main. dfg[ main. entry_block( ) ] . instructions( ) . len( ) , 5 ) ;
1008
+
1009
+ // Expected output:
1010
+ //
1011
+ // acir(inline) fn main f0 {
1012
+ // b0(v0: [Field; 2]):
1013
+ // v2 = array_get v0, index u32 0
1014
+ // return v2
1015
+ // }
1016
+ let ssa = ssa. dead_instruction_elimination ( ) ;
1017
+ let main = ssa. main ( ) ;
1018
+
1019
+ let instructions = main. dfg [ main. entry_block ( ) ] . instructions ( ) ;
1020
+ assert_eq ! ( instructions. len( ) , 1 ) ;
1021
+ assert ! ( matches!( & main. dfg[ instructions[ 0 ] ] , Instruction :: ArrayGet { .. } ) ) ;
1022
+ }
782
1023
}
0 commit comments