OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 the V8 project authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "src/compiler/store-store-elimination.h" | |
6 | |
7 #include "src/compiler/all-nodes.h" | |
8 #include "src/compiler/js-graph.h" | |
9 #include "src/compiler/node-properties.h" | |
10 #include "src/compiler/simplified-operator.h" | |
11 | |
12 namespace v8 { | |
13 namespace internal { | |
14 namespace compiler { | |
15 | |
16 // A simple store-store elimination. When the effect chain contains the | |
17 // following sequence, | |
18 // | |
19 // - StoreField[[+off_1]](x1, y1) | |
20 // - StoreField[[+off_2]](x2, y2) | |
21 // - StoreField[[+off_3]](x3, y3) | |
22 // ... | |
23 // - StoreField[[+off_n]](xn, yn) | |
24 // | |
25 // where the xes are the objects and the ys are the values to be stored, then | |
26 // we are going to say that a store is superfluous if the same offset of the | |
27 // same object will be stored to in the future. If off_i == off_j and xi == xj | |
28 // and i < j, then we optimize the i'th StoreField away. | |
29 // | |
30 // This optimization should be initiated on the last StoreField in such a | |
31 // sequence. | |
32 // | |
33 // The algorithm works by walking the effect chain from the last StoreField | |
34 // upwards. While walking, we maintain a map {futureStore} from offsets to | |
35 // nodes; initially it is empty. As we walk the effect chain upwards, if | |
36 // futureStore[off] = n, then any store to node {n} with offset {off} is | |
37 // guaranteed to be useless because we do a full-width[1] store to that offset | |
38 // of that object in the near future anyway. For example, for this effect | |
39 // chain | |
40 // | |
41 // 71: StoreField(60, 0) | |
42 // 72: StoreField(65, 8) | |
43 // 73: StoreField(63, 8) | |
44 // 74: StoreField(65, 16) | |
45 // 75: StoreField(62, 8) | |
46 // | |
47 // just before we get to 72, we will have futureStore = {8: 63, 16: 65}. | |
48 // | |
49 // Here is the complete process. | |
50 // | |
51 // - We are at the end of a sequence of consecutive StoreFields. | |
52 // - We start out with futureStore = empty. | |
53 // - We then walk the effect chain upwards to find the next StoreField [2]. | |
54 // | |
55 // 1. If the offset is not a key of {futureStore} yet, we put it in. | |
56 // 2. If the offset is a key of {futureStore}, but futureStore[offset] is a | |
57 // different node, we overwrite futureStore[offset] with the current node. | |
58 // 3. If the offset is a key of {futureStore} and futureStore[offset] equals | |
59 // this node, we eliminate this StoreField. | |
60 // | |
61 // As long as the current effect input points to a node with a single effect | |
62 // output, and as long as its opcode is StoreField, we keep traversing | |
63 // upwards. | |
64 // | |
65 // [1] This optimization is unsound if we optimize away a store to an offset | |
66 // because we store to the same offset in the future, even though the future | |
67 // store is narrower than the store we optimize away. Therefore, in case (1) | |
68 // and (2) we only add/overwrite to the dictionary when the field access has | |
69 // maximal size. For simplicity of implementation, we do not try to detect | |
70 // case (3). | |
71 // | |
72 // [2] We make sure that we only traverse the linear part, that is, the part | |
73 // where every node has exactly one incoming and one outgoing effect edge. | |
74 // Also, we only keep walking upwards as long as we keep finding consecutive | |
75 // StoreFields on the same node. | |
76 | |
77 StoreStoreElimination::StoreStoreElimination(JSGraph* js_graph, Zone* temp_zone) | |
78 : jsgraph_(js_graph), temp_zone_(temp_zone) {} | |
79 | |
80 StoreStoreElimination::~StoreStoreElimination() {} | |
81 | |
82 void StoreStoreElimination::Run() { | |
83 // The store-store elimination performs work on chains of certain types of | |
84 // nodes. The elimination must be invoked on the lowest node in such a | |
85 // chain; we have a helper function IsEligibleNode that returns true | |
86 // precisely on the lowest node in such a chain. | |
87 // | |
88 // Because the elimination removes nodes from the graph, even remove nodes | |
89 // that the elimination was not invoked on, we cannot use a normal | |
90 // AdvancedReducer but we manually find which nodes to invoke the | |
91 // elimination on. Then in a next step, we invoke the elimination for each | |
92 // node that was eligible. | |
93 | |
94 NodeVector eligible(temp_zone()); // loops over all nodes | |
95 AllNodes all(temp_zone(), jsgraph()->graph()); | |
96 | |
97 for (Node* node : all.live) { | |
98 if (IsEligibleNode(node)) { | |
99 eligible.push_back(node); | |
100 } | |
101 } | |
102 | |
103 for (Node* node : eligible) { | |
104 ReduceEligibleNode(node); | |
105 } | |
106 } | |
107 | |
108 namespace { | |
109 | |
110 // 16 bits was chosen fairly arbitrarily; it seems enough now. 8 bits is too | |
111 // few. | |
112 typedef uint16_t Offset; | |
113 | |
114 // To safely cast an offset from a FieldAccess, which has a wider range | |
115 // (namely int). | |
116 Offset ToOffset(int offset) { | |
117 CHECK(0 <= offset && offset < (1 << 8 * sizeof(Offset))); | |
118 return (Offset)offset; | |
119 } | |
120 | |
121 Offset ToOffset(const FieldAccess& access) { return ToOffset(access.offset); } | |
122 | |
123 // If node has a single effect use, return that node. If node has no or | |
124 // multiple effect uses, return nullptr. | |
125 Node* SingleEffectUse(Node* node) { | |
126 Node* last_use = nullptr; | |
127 for (Edge edge : node->use_edges()) { | |
128 if (!NodeProperties::IsEffectEdge(edge)) { | |
129 continue; | |
130 } | |
131 if (last_use != nullptr) { | |
132 // more than one | |
133 return nullptr; | |
134 } | |
135 last_use = edge.from(); | |
136 DCHECK_NOT_NULL(last_use); | |
137 } | |
138 return last_use; | |
139 } | |
140 | |
141 // Return true if node is the last consecutive StoreField node in a linear | |
142 // part of the effect chain. | |
143 bool IsEndOfStoreFieldChain(Node* node) { | |
144 Node* next_on_chain = SingleEffectUse(node); | |
145 return (next_on_chain == nullptr || | |
146 next_on_chain->op()->opcode() != IrOpcode::kStoreField); | |
147 } | |
148 | |
149 // The argument must be a StoreField node. If there is a node before it in the | |
150 // effect chain, and if this part of the effect chain is linear (no other | |
151 // effect uses of that previous node), then return that previous node. | |
152 // Otherwise, return nullptr. | |
153 // | |
154 // The returned node need not be a StoreField. | |
155 Node* PreviousEffectBeforeStoreField(Node* node) { | |
156 DCHECK_EQ(node->op()->opcode(), IrOpcode::kStoreField); | |
157 DCHECK_EQ(node->op()->EffectInputCount(), 1); | |
158 | |
159 Node* previous = NodeProperties::GetEffectInput(node); | |
160 if (previous != nullptr && node == SingleEffectUse(previous)) { | |
161 return previous; | |
162 } else { | |
163 return nullptr; | |
164 } | |
165 } | |
166 | |
167 } // namespace | |
168 | |
169 bool StoreStoreElimination::IsEligibleNode(Node* node) { | |
170 return (node->op()->opcode() == IrOpcode::kStoreField) && | |
171 IsEndOfStoreFieldChain(node); | |
172 } | |
173 | |
174 void StoreStoreElimination::ReduceEligibleNode(Node* node) { | |
175 DCHECK(IsEligibleNode(node)); | |
176 | |
177 if (FLAG_trace_store_elimination) { | |
178 printf("** StoreStoreElimination::ReduceEligibleNode activated: #%d\n", | |
Jarin
2016/06/23 09:58:29
printf -> PrintF (here and elsewhere)
bgeron
2016/06/23 12:03:05
Done.
| |
179 node->id()); | |
180 } | |
181 auto log = [](NodeId id, Offset off, const char* msg) { | |
Jarin
2016/06/23 09:58:28
This does not have to be a lambda, please put it i
bgeron
2016/06/23 12:03:05
Done.
| |
182 if (FLAG_trace_store_elimination) { | |
183 printf( | |
184 " StoreStoreElimination::ReduceEligibleNode: " | |
185 "#%d[[+%d]] -- %s\n", | |
186 id, off, msg); | |
187 } | |
188 }; | |
189 | |
190 // Initialize empty futureStore. | |
191 ZoneMap<Offset, Node*> futureStore(temp_zone()); | |
192 | |
193 Node* current_node = node; | |
194 | |
195 do { | |
196 FieldAccess access = OpParameter<FieldAccess>(current_node->op()); | |
197 Offset offset = ToOffset(access); | |
198 Node* object_input = current_node->InputAt(0); | |
199 | |
200 Node* previous = PreviousEffectBeforeStoreField(current_node); | |
201 | |
202 if (access.machine_type.IsWidestSize()) { | |
Jarin
2016/06/23 09:58:28
I still think this is not a good idea. If someone
bgeron
2016/06/23 12:03:05
Done.
| |
203 // Try to insert. If it was present, this will preserve the original | |
204 // value. | |
205 auto insert_result = | |
206 futureStore.insert(std::make_pair(offset, object_input)); | |
207 if (insert_result.second) { | |
208 // Key was not present. This means that there is no matching | |
209 // StoreField to this offset in the future, so we cannot optimize | |
210 // current_node away. However, we will record the current StoreField | |
211 // in futureStore, and continue ascending up the chain. | |
212 log(current_node->id(), offset, "wide, key not present"); | |
213 } else if (insert_result.first->second != object_input) { | |
214 // Key was present, and the value did not equal object_input. This | |
215 // means that there is a StoreField to this offset in the future, but | |
216 // the object instance comes from a different Node. We pessimistically | |
217 // assume that we cannot optimize current_node away. However, we will | |
218 // record the current StoreField in futureStore, and continue | |
219 // ascending up the chain. | |
220 insert_result.first->second = object_input; | |
221 log(current_node->id(), offset, "wide, diff object"); | |
222 } else { | |
223 // Key was present, and the value equalled object_input. This means that | |
224 // soon after in the effect chain, we will do a StoreField to the same | |
225 // object with the same offset, therefore current_node can be optimized | |
226 // away. We don't need to update futureStore. | |
227 | |
228 Node* previous_effect = NodeProperties::GetEffectInput(current_node); | |
229 | |
230 NodeProperties::ReplaceUses(current_node, nullptr, previous_effect, | |
231 nullptr, nullptr); | |
232 current_node->Kill(); | |
233 log(current_node->id(), offset, "wide, eliminated"); | |
234 } | |
235 } else { | |
236 log(current_node->id(), offset, "narrow, not eliminated"); | |
237 } | |
238 | |
239 // Regardless of whether we eliminated node {current}, we want to | |
240 // continue walking up the effect chain. | |
241 | |
242 current_node = previous; | |
243 } while (current_node != nullptr && | |
244 current_node->op()->opcode() == IrOpcode::kStoreField); | |
245 | |
246 if (FLAG_trace_store_elimination) { | |
247 printf( | |
248 " StoreStoreElimination::ReduceEligibleNode: " | |
249 "stop chain traversal\n"); | |
250 } | |
251 } | |
252 | |
253 } // namespace compiler | |
254 } // namespace internal | |
255 } // namespace v8 | |
OLD | NEW |