blob: f37de90ff3d37311d0d3d4fa6916967bed036b87 [file] [log] [blame]
machenbach@chromium.org528ce022013-09-23 14:09:36 +00001// Copyright 2013 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6// * Redistributions of source code must retain the above copyright
7// notice, this list of conditions and the following disclaimer.
8// * Redistributions in binary form must reproduce the above
9// copyright notice, this list of conditions and the following
10// disclaimer in the documentation and/or other materials provided
11// with the distribution.
12// * Neither the name of Google Inc. nor the names of its
13// contributors may be used to endorse or promote products derived
14// from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28#include "hydrogen-alias-analysis.h"
29#include "hydrogen-load-elimination.h"
30#include "hydrogen-instructions.h"
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000031#include "hydrogen-flow-engine.h"
machenbach@chromium.org528ce022013-09-23 14:09:36 +000032
33namespace v8 {
34namespace internal {
35
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000036#define GLOBAL true
37#define TRACE(x) if (FLAG_trace_load_elimination) PrintF x
38
machenbach@chromium.org528ce022013-09-23 14:09:36 +000039static const int kMaxTrackedFields = 16;
40static const int kMaxTrackedObjects = 5;
41
42// An element in the field approximation list.
43class HFieldApproximation : public ZoneObject {
44 public: // Just a data blob.
45 HValue* object_;
46 HLoadNamedField* last_load_;
47 HValue* last_value_;
48 HFieldApproximation* next_;
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000049
50 // Recursively copy the entire linked list of field approximations.
51 HFieldApproximation* Copy(Zone* zone) {
52 if (this == NULL) return NULL;
53 HFieldApproximation* copy = new(zone) HFieldApproximation();
54 copy->object_ = this->object_;
55 copy->last_load_ = this->last_load_;
56 copy->last_value_ = this->last_value_;
57 copy->next_ = this->next_->Copy(zone);
58 return copy;
59 }
machenbach@chromium.org528ce022013-09-23 14:09:36 +000060};
61
62
63// The main datastructure used during load/store elimination. Each in-object
64// field is tracked separately. For each field, store a list of known field
65// values for known objects.
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000066class HLoadEliminationTable : public ZoneObject {
machenbach@chromium.org528ce022013-09-23 14:09:36 +000067 public:
68 HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing)
69 : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { }
70
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000071 // The main processing of instructions.
72 HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) {
73 switch (instr->opcode()) {
74 case HValue::kLoadNamedField: {
75 HLoadNamedField* l = HLoadNamedField::cast(instr);
76 TRACE((" process L%d field %d (o%d)\n",
77 instr->id(),
78 FieldOf(l->access()),
79 l->object()->ActualValue()->id()));
80 HValue* result = load(l);
81 if (result != instr) {
82 // The load can be replaced with a previous load or a value.
83 TRACE((" replace L%d -> v%d\n", instr->id(), result->id()));
84 instr->DeleteAndReplaceWith(result);
85 }
86 break;
87 }
88 case HValue::kStoreNamedField: {
89 HStoreNamedField* s = HStoreNamedField::cast(instr);
90 TRACE((" process S%d field %d (o%d) = v%d\n",
91 instr->id(),
92 FieldOf(s->access()),
93 s->object()->ActualValue()->id(),
94 s->value()->id()));
95 HValue* result = store(s);
96 if (result == NULL) {
97 // The store is redundant. Remove it.
98 TRACE((" remove S%d\n", instr->id()));
99 instr->DeleteAndReplaceWith(NULL);
100 }
101 break;
102 }
103 default: {
104 if (instr->CheckGVNFlag(kChangesInobjectFields)) {
105 TRACE((" kill-all i%d\n", instr->id()));
106 Kill();
107 break;
108 }
109 if (instr->CheckGVNFlag(kChangesMaps)) {
110 TRACE((" kill-maps i%d\n", instr->id()));
111 KillOffset(JSObject::kMapOffset);
112 }
113 if (instr->CheckGVNFlag(kChangesElementsKind)) {
114 TRACE((" kill-elements-kind i%d\n", instr->id()));
115 KillOffset(JSObject::kMapOffset);
116 KillOffset(JSObject::kElementsOffset);
117 }
118 if (instr->CheckGVNFlag(kChangesElementsPointer)) {
119 TRACE((" kill-elements i%d\n", instr->id()));
120 KillOffset(JSObject::kElementsOffset);
121 }
122 if (instr->CheckGVNFlag(kChangesOsrEntries)) {
123 TRACE((" kill-osr i%d\n", instr->id()));
124 Kill();
125 }
126 }
127 // Improvements possible:
128 // - learn from HCheckMaps for field 0
129 // - remove unobservable stores (write-after-write)
130 // - track cells
131 // - track globals
132 // - track roots
133 }
134 return this;
135 }
136
137 // Support for global analysis with HFlowEngine: Copy state to sucessor block.
138 HLoadEliminationTable* Copy(HBasicBlock* succ, Zone* zone) {
139 HLoadEliminationTable* copy =
140 new(zone) HLoadEliminationTable(zone, aliasing_);
141 copy->EnsureFields(fields_.length());
142 for (int i = 0; i < fields_.length(); i++) {
143 copy->fields_[i] = fields_[i]->Copy(zone);
144 }
145 if (FLAG_trace_load_elimination) {
146 TRACE((" copy-to B%d\n", succ->block_id()));
147 copy->Print();
148 }
149 return copy;
150 }
151
152 // Support for global analysis with HFlowEngine: Merge this state with
153 // the other incoming state.
154 HLoadEliminationTable* Merge(HBasicBlock* succ,
155 HLoadEliminationTable* that, Zone* zone) {
156 if (that->fields_.length() < fields_.length()) {
157 // Drop fields not in the other table.
158 fields_.Rewind(that->fields_.length());
159 }
160 for (int i = 0; i < fields_.length(); i++) {
161 // Merge the field approximations for like fields.
162 HFieldApproximation* approx = fields_[i];
163 HFieldApproximation* prev = NULL;
164 while (approx != NULL) {
165 // TODO(titzer): Merging is O(N * M); sort?
166 HFieldApproximation* other = that->Find(approx->object_, i);
167 if (other == NULL || !Equal(approx->last_value_, other->last_value_)) {
168 // Kill an entry that doesn't agree with the other value.
169 if (prev != NULL) {
170 prev->next_ = approx->next_;
171 } else {
172 fields_[i] = approx->next_;
173 }
174 approx = approx->next_;
175 continue;
176 }
177 prev = approx;
178 approx = approx->next_;
179 }
180 }
181 return this;
182 }
183
184 friend class HLoadEliminationEffects; // Calls Kill() and others.
185 friend class HLoadEliminationPhase;
186
187 private:
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000188 // Process a load instruction, updating internal table state. If a previous
189 // load or store for this object and field exists, return the new value with
190 // which the load should be replaced. Otherwise, return {instr}.
191 HValue* load(HLoadNamedField* instr) {
192 int field = FieldOf(instr->access());
193 if (field < 0) return instr;
194
195 HValue* object = instr->object()->ActualValue();
196 HFieldApproximation* approx = FindOrCreate(object, field);
197
198 if (approx->last_value_ == NULL) {
199 // Load is not redundant. Fill out a new entry.
200 approx->last_load_ = instr;
201 approx->last_value_ = instr;
202 return instr;
203 } else {
204 // Eliminate the load. Reuse previously stored value or load instruction.
205 return approx->last_value_;
206 }
207 }
208
209 // Process a store instruction, updating internal table state. If a previous
210 // store to the same object and field makes this store redundant (e.g. because
211 // the stored values are the same), return NULL indicating that this store
212 // instruction is redundant. Otherwise, return {instr}.
213 HValue* store(HStoreNamedField* instr) {
214 int field = FieldOf(instr->access());
bmeurer@chromium.org71f9fca2013-10-22 08:00:09 +0000215 if (field < 0) return KillIfMisaligned(instr);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000216
217 HValue* object = instr->object()->ActualValue();
218 HValue* value = instr->value();
219
220 // Kill non-equivalent may-alias entries.
221 KillFieldInternal(object, field, value);
222 if (instr->has_transition()) {
223 // A transition store alters the map of the object.
224 // TODO(titzer): remember the new map (a constant) for the object.
225 KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL);
226 }
227 HFieldApproximation* approx = FindOrCreate(object, field);
228
229 if (Equal(approx->last_value_, value)) {
230 // The store is redundant because the field already has this value.
231 return NULL;
232 } else {
233 // The store is not redundant. Update the entry.
234 approx->last_load_ = NULL;
235 approx->last_value_ = value;
236 return instr;
237 }
238 }
239
240 // Kill everything in this table.
241 void Kill() {
242 fields_.Rewind(0);
243 }
244
245 // Kill all entries matching the given offset.
246 void KillOffset(int offset) {
247 int field = FieldOf(offset);
248 if (field >= 0 && field < fields_.length()) {
249 fields_[field] = NULL;
250 }
251 }
252
bmeurer@chromium.org71f9fca2013-10-22 08:00:09 +0000253 // Kill all entries aliasing the given store.
254 void KillStore(HStoreNamedField* s) {
255 int field = FieldOf(s->access());
256 if (field >= 0) {
257 KillFieldInternal(s->object()->ActualValue(), field, s->value());
258 } else {
259 KillIfMisaligned(s);
260 }
261 }
262
263 // Kill multiple entries in the case of a misaligned store.
264 HValue* KillIfMisaligned(HStoreNamedField* instr) {
265 HObjectAccess access = instr->access();
266 if (access.IsInobject()) {
267 int offset = access.offset();
268 if ((offset % kPointerSize) != 0) {
269 // Kill the field containing the first word of the access.
270 HValue* object = instr->object()->ActualValue();
271 int field = offset / kPointerSize;
272 KillFieldInternal(object, field, NULL);
273
274 // Kill the next field in case of overlap.
machenbach@chromium.org935a7792013-11-12 09:05:18 +0000275 int size = access.representation().size();
bmeurer@chromium.org71f9fca2013-10-22 08:00:09 +0000276 int next_field = (offset + size - 1) / kPointerSize;
277 if (next_field != field) KillFieldInternal(object, next_field, NULL);
278 }
279 }
280 return instr;
281 }
282
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000283 // Find an entry for the given object and field pair.
284 HFieldApproximation* Find(HValue* object, int field) {
285 // Search for a field approximation for this object.
286 HFieldApproximation* approx = fields_[field];
287 while (approx != NULL) {
288 if (aliasing_->MustAlias(object, approx->object_)) return approx;
289 approx = approx->next_;
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000290 }
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000291 return NULL;
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000292 }
293
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000294 // Find or create an entry for the given object and field pair.
295 HFieldApproximation* FindOrCreate(HValue* object, int field) {
296 EnsureFields(field + 1);
297
298 // Search for a field approximation for this object.
299 HFieldApproximation* approx = fields_[field];
300 int count = 0;
301 while (approx != NULL) {
302 if (aliasing_->MustAlias(object, approx->object_)) return approx;
303 count++;
304 approx = approx->next_;
305 }
306
307 if (count >= kMaxTrackedObjects) {
308 // Pull the last entry off the end and repurpose it for this object.
309 approx = ReuseLastApproximation(field);
310 } else {
311 // Allocate a new entry.
312 approx = new(zone_) HFieldApproximation();
313 }
314
315 // Insert the entry at the head of the list.
316 approx->object_ = object;
317 approx->last_load_ = NULL;
318 approx->last_value_ = NULL;
319 approx->next_ = fields_[field];
320 fields_[field] = approx;
321
322 return approx;
323 }
324
325 // Kill all entries for a given field that _may_ alias the given object
326 // and do _not_ have the given value.
327 void KillFieldInternal(HValue* object, int field, HValue* value) {
328 if (field >= fields_.length()) return; // Nothing to do.
329
330 HFieldApproximation* approx = fields_[field];
331 HFieldApproximation* prev = NULL;
332 while (approx != NULL) {
333 if (aliasing_->MayAlias(object, approx->object_)) {
334 if (!Equal(approx->last_value_, value)) {
335 // Kill an aliasing entry that doesn't agree on the value.
336 if (prev != NULL) {
337 prev->next_ = approx->next_;
338 } else {
339 fields_[field] = approx->next_;
340 }
341 approx = approx->next_;
342 continue;
343 }
344 }
345 prev = approx;
346 approx = approx->next_;
347 }
348 }
349
350 bool Equal(HValue* a, HValue* b) {
351 if (a == b) return true;
352 if (a != NULL && b != NULL) return a->Equals(b);
353 return false;
354 }
355
356 // Remove the last approximation for a field so that it can be reused.
357 // We reuse the last entry because it was the first inserted and is thus
358 // farthest away from the current instruction.
359 HFieldApproximation* ReuseLastApproximation(int field) {
360 HFieldApproximation* approx = fields_[field];
361 ASSERT(approx != NULL);
362
363 HFieldApproximation* prev = NULL;
364 while (approx->next_ != NULL) {
365 prev = approx;
366 approx = approx->next_;
367 }
368 if (prev != NULL) prev->next_ = NULL;
369 return approx;
370 }
371
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000372 // Compute the field index for the given object access; -1 if not tracked.
373 int FieldOf(HObjectAccess access) {
374 return access.IsInobject() ? FieldOf(access.offset()) : -1;
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000375 }
376
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000377 // Compute the field index for the given in-object offset; -1 if not tracked.
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000378 int FieldOf(int offset) {
379 if (offset >= kMaxTrackedFields * kPointerSize) return -1;
bmeurer@chromium.org71f9fca2013-10-22 08:00:09 +0000380 // TODO(titzer): track misaligned loads in a separate list?
381 if ((offset % kPointerSize) != 0) return -1; // Ignore misaligned accesses.
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000382 return offset / kPointerSize;
383 }
384
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000385 // Ensure internal storage for the given number of fields.
386 void EnsureFields(int num_fields) {
387 if (fields_.length() < num_fields) {
388 fields_.AddBlock(NULL, num_fields - fields_.length(), zone_);
389 }
390 }
391
392 // Print this table to stdout.
393 void Print() {
394 for (int i = 0; i < fields_.length(); i++) {
395 PrintF(" field %d: ", i);
396 for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) {
397 PrintF("[o%d =", a->object_->id());
398 if (a->last_load_ != NULL) PrintF(" L%d", a->last_load_->id());
399 if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id());
400 PrintF("] ");
401 }
402 PrintF("\n");
403 }
404 }
405
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000406 Zone* zone_;
407 ZoneList<HFieldApproximation*> fields_;
408 HAliasAnalyzer* aliasing_;
409};
410
411
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000412// Support for HFlowEngine: collect store effects within loops.
413class HLoadEliminationEffects : public ZoneObject {
414 public:
415 explicit HLoadEliminationEffects(Zone* zone)
416 : zone_(zone),
417 maps_stored_(false),
418 fields_stored_(false),
419 elements_stored_(false),
420 stores_(5, zone) { }
421
422 inline bool Disabled() {
423 return false; // Effects are _not_ disabled.
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000424 }
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000425
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000426 // Process a possibly side-effecting instruction.
427 void Process(HInstruction* instr, Zone* zone) {
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000428 switch (instr->opcode()) {
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000429 case HValue::kStoreNamedField: {
430 stores_.Add(HStoreNamedField::cast(instr), zone_);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000431 break;
432 }
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000433 case HValue::kOsrEntry: {
434 // Kill everything. Loads must not be hoisted past the OSR entry.
435 maps_stored_ = true;
436 fields_stored_ = true;
437 elements_stored_ = true;
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000438 }
439 default: {
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000440 fields_stored_ |= instr->CheckGVNFlag(kChangesInobjectFields);
441 maps_stored_ |= instr->CheckGVNFlag(kChangesMaps);
442 maps_stored_ |= instr->CheckGVNFlag(kChangesElementsKind);
443 elements_stored_ |= instr->CheckGVNFlag(kChangesElementsKind);
444 elements_stored_ |= instr->CheckGVNFlag(kChangesElementsPointer);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000445 }
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000446 }
447 }
448
449 // Apply these effects to the given load elimination table.
450 void Apply(HLoadEliminationTable* table) {
451 if (fields_stored_) {
452 table->Kill();
453 return;
454 }
455 if (maps_stored_) {
456 table->KillOffset(JSObject::kMapOffset);
457 }
458 if (elements_stored_) {
459 table->KillOffset(JSObject::kElementsOffset);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000460 }
461
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000462 // Kill non-agreeing fields for each store contained in these effects.
463 for (int i = 0; i < stores_.length(); i++) {
bmeurer@chromium.org71f9fca2013-10-22 08:00:09 +0000464 table->KillStore(stores_[i]);
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000465 }
466 }
467
468 // Union these effects with the other effects.
469 void Union(HLoadEliminationEffects* that, Zone* zone) {
470 maps_stored_ |= that->maps_stored_;
471 fields_stored_ |= that->fields_stored_;
472 elements_stored_ |= that->elements_stored_;
473 for (int i = 0; i < that->stores_.length(); i++) {
474 stores_.Add(that->stores_[i], zone);
475 }
476 }
477
478 private:
479 Zone* zone_;
480 bool maps_stored_ : 1;
481 bool fields_stored_ : 1;
482 bool elements_stored_ : 1;
483 ZoneList<HStoreNamedField*> stores_;
484};
485
486
487// The main routine of the analysis phase. Use the HFlowEngine for either a
488// local or a global analysis.
489void HLoadEliminationPhase::Run() {
490 HFlowEngine<HLoadEliminationTable, HLoadEliminationEffects>
491 engine(graph(), zone());
492 HAliasAnalyzer aliasing;
493 HLoadEliminationTable* table =
494 new(zone()) HLoadEliminationTable(zone(), &aliasing);
495
496 if (GLOBAL) {
497 // Perform a global analysis.
498 engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table);
499 } else {
500 // Perform only local analysis.
501 for (int i = 0; i < graph()->blocks()->length(); i++) {
502 table->Kill();
503 engine.AnalyzeOneBlock(graph()->blocks()->at(i), table);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000504 }
505 }
506}
507
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000508} } // namespace v8::internal