1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "write_barrier_elimination.h"
18
19 #include "base/arena_allocator.h"
20 #include "base/scoped_arena_allocator.h"
21 #include "base/scoped_arena_containers.h"
22 #include "optimizing/nodes.h"
23
24 namespace art HIDDEN {
25
26 class WBEVisitor final : public HGraphVisitor {
27 public:
WBEVisitor(HGraph * graph,OptimizingCompilerStats * stats)28 WBEVisitor(HGraph* graph, OptimizingCompilerStats* stats)
29 : HGraphVisitor(graph),
30 scoped_allocator_(graph->GetArenaStack()),
31 current_write_barriers_(scoped_allocator_.Adapter(kArenaAllocWBE)),
32 stats_(stats) {}
33
VisitBasicBlock(HBasicBlock * block)34 void VisitBasicBlock(HBasicBlock* block) override {
35 // We clear the map to perform this optimization only in the same block. Doing it across blocks
36 // would entail non-trivial merging of states.
37 current_write_barriers_.clear();
38 HGraphVisitor::VisitBasicBlock(block);
39 }
40
VisitInstanceFieldSet(HInstanceFieldSet * instruction)41 void VisitInstanceFieldSet(HInstanceFieldSet* instruction) override {
42 DCHECK(!instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()));
43
44 if (instruction->GetFieldType() != DataType::Type::kReference ||
45 instruction->GetValue()->IsNullConstant()) {
46 instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
47 return;
48 }
49
50 MaybeRecordStat(stats_, MethodCompilationStat::kPossibleWriteBarrier);
51 HInstruction* obj = HuntForOriginalReference(instruction->InputAt(0));
52 auto it = current_write_barriers_.find(obj);
53 if (it != current_write_barriers_.end()) {
54 DCHECK(it->second->IsInstanceFieldSet());
55 DCHECK(it->second->AsInstanceFieldSet()->GetWriteBarrierKind() !=
56 WriteBarrierKind::kDontEmit);
57 DCHECK_EQ(it->second->GetBlock(), instruction->GetBlock());
58 it->second->AsInstanceFieldSet()->SetWriteBarrierKind(WriteBarrierKind::kEmitNoNullCheck);
59 instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
60 MaybeRecordStat(stats_, MethodCompilationStat::kRemovedWriteBarrier);
61 } else {
62 const bool inserted = current_write_barriers_.insert({obj, instruction}).second;
63 DCHECK(inserted);
64 DCHECK(instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
65 }
66 }
67
VisitStaticFieldSet(HStaticFieldSet * instruction)68 void VisitStaticFieldSet(HStaticFieldSet* instruction) override {
69 DCHECK(!instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()));
70
71 if (instruction->GetFieldType() != DataType::Type::kReference ||
72 instruction->GetValue()->IsNullConstant()) {
73 instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
74 return;
75 }
76
77 MaybeRecordStat(stats_, MethodCompilationStat::kPossibleWriteBarrier);
78 HInstruction* cls = HuntForOriginalReference(instruction->InputAt(0));
79 auto it = current_write_barriers_.find(cls);
80 if (it != current_write_barriers_.end()) {
81 DCHECK(it->second->IsStaticFieldSet());
82 DCHECK(it->second->AsStaticFieldSet()->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
83 DCHECK_EQ(it->second->GetBlock(), instruction->GetBlock());
84 it->second->AsStaticFieldSet()->SetWriteBarrierKind(WriteBarrierKind::kEmitNoNullCheck);
85 instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
86 MaybeRecordStat(stats_, MethodCompilationStat::kRemovedWriteBarrier);
87 } else {
88 const bool inserted = current_write_barriers_.insert({cls, instruction}).second;
89 DCHECK(inserted);
90 DCHECK(instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
91 }
92 }
93
VisitArraySet(HArraySet * instruction)94 void VisitArraySet(HArraySet* instruction) override {
95 if (instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC())) {
96 ClearCurrentValues();
97 }
98
99 if (instruction->GetComponentType() != DataType::Type::kReference ||
100 instruction->GetValue()->IsNullConstant()) {
101 instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
102 return;
103 }
104
105 HInstruction* arr = HuntForOriginalReference(instruction->InputAt(0));
106 MaybeRecordStat(stats_, MethodCompilationStat::kPossibleWriteBarrier);
107 auto it = current_write_barriers_.find(arr);
108 if (it != current_write_barriers_.end()) {
109 DCHECK(it->second->IsArraySet());
110 DCHECK(it->second->AsArraySet()->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
111 DCHECK_EQ(it->second->GetBlock(), instruction->GetBlock());
112 // We never skip the null check in ArraySets so that value is already set.
113 DCHECK(it->second->AsArraySet()->GetWriteBarrierKind() == WriteBarrierKind::kEmitNoNullCheck);
114 instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
115 MaybeRecordStat(stats_, MethodCompilationStat::kRemovedWriteBarrier);
116 } else {
117 const bool inserted = current_write_barriers_.insert({arr, instruction}).second;
118 DCHECK(inserted);
119 DCHECK(instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
120 }
121 }
122
VisitInstruction(HInstruction * instruction)123 void VisitInstruction(HInstruction* instruction) override {
124 if (instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC())) {
125 ClearCurrentValues();
126 }
127 }
128
129 private:
ClearCurrentValues()130 void ClearCurrentValues() { current_write_barriers_.clear(); }
131
HuntForOriginalReference(HInstruction * ref) const132 HInstruction* HuntForOriginalReference(HInstruction* ref) const {
133 // An original reference can be transformed by instructions like:
134 // i0 NewArray
135 // i1 HInstruction(i0) <-- NullCheck, BoundType, IntermediateAddress.
136 // i2 ArraySet(i1, index, value)
137 DCHECK(ref != nullptr);
138 while (ref->IsNullCheck() || ref->IsBoundType() || ref->IsIntermediateAddress()) {
139 ref = ref->InputAt(0);
140 }
141 return ref;
142 }
143
144 ScopedArenaAllocator scoped_allocator_;
145
146 // Stores a map of <Receiver, InstructionWhereTheWriteBarrierIs>.
147 // `InstructionWhereTheWriteBarrierIs` is used for DCHECKs only.
148 ScopedArenaHashMap<HInstruction*, HInstruction*> current_write_barriers_;
149
150 OptimizingCompilerStats* const stats_;
151
152 DISALLOW_COPY_AND_ASSIGN(WBEVisitor);
153 };
154
Run()155 bool WriteBarrierElimination::Run() {
156 WBEVisitor wbe_visitor(graph_, stats_);
157 wbe_visitor.VisitReversePostOrder();
158 return true;
159 }
160
161 } // namespace art
162