blob: 88963fc18b3adc9dc56d6da43efe878c7d9dc6a6 [file] [log] [blame]
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001// Copyright 2013 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/crankshaft/hydrogen-load-elimination.h"
6
7#include "src/crankshaft/hydrogen-alias-analysis.h"
8#include "src/crankshaft/hydrogen-flow-engine.h"
9#include "src/crankshaft/hydrogen-instructions.h"
10
11namespace v8 {
12namespace internal {
13
14#define GLOBAL true
15#define TRACE(x) if (FLAG_trace_load_elimination) PrintF x
16
17static const int kMaxTrackedFields = 16;
18static const int kMaxTrackedObjects = 5;
19
20// An element in the field approximation list.
21class HFieldApproximation : public ZoneObject {
22 public: // Just a data blob.
23 HValue* object_;
24 HValue* last_value_;
25 HFieldApproximation* next_;
26
27 // Recursively copy the entire linked list of field approximations.
28 HFieldApproximation* Copy(Zone* zone) {
29 HFieldApproximation* copy = new(zone) HFieldApproximation();
30 copy->object_ = this->object_;
31 copy->last_value_ = this->last_value_;
32 copy->next_ = this->next_ == NULL ? NULL : this->next_->Copy(zone);
33 return copy;
34 }
35};
36
37
38// The main datastructure used during load/store elimination. Each in-object
39// field is tracked separately. For each field, store a list of known field
40// values for known objects.
41class HLoadEliminationTable : public ZoneObject {
42 public:
43 HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing)
44 : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { }
45
46 // The main processing of instructions.
47 HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) {
48 switch (instr->opcode()) {
49 case HValue::kLoadNamedField: {
50 HLoadNamedField* l = HLoadNamedField::cast(instr);
51 TRACE((" process L%d field %d (o%d)\n",
52 instr->id(),
53 FieldOf(l->access()),
54 l->object()->ActualValue()->id()));
55 HValue* result = load(l);
56 if (result != instr && l->CanBeReplacedWith(result)) {
57 // The load can be replaced with a previous load or a value.
58 TRACE((" replace L%d -> v%d\n", instr->id(), result->id()));
59 instr->DeleteAndReplaceWith(result);
60 }
61 break;
62 }
63 case HValue::kStoreNamedField: {
64 HStoreNamedField* s = HStoreNamedField::cast(instr);
65 TRACE((" process S%d field %d (o%d) = v%d\n",
66 instr->id(),
67 FieldOf(s->access()),
68 s->object()->ActualValue()->id(),
69 s->value()->id()));
70 HValue* result = store(s);
71 if (result == NULL) {
72 // The store is redundant. Remove it.
73 TRACE((" remove S%d\n", instr->id()));
74 instr->DeleteAndReplaceWith(NULL);
75 }
76 break;
77 }
78 case HValue::kTransitionElementsKind: {
79 HTransitionElementsKind* t = HTransitionElementsKind::cast(instr);
80 HValue* object = t->object()->ActualValue();
81 KillFieldInternal(object, FieldOf(JSArray::kElementsOffset), NULL);
82 KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL);
83 break;
84 }
85 default: {
86 if (instr->CheckChangesFlag(kInobjectFields)) {
87 TRACE((" kill-all i%d\n", instr->id()));
88 Kill();
89 break;
90 }
91 if (instr->CheckChangesFlag(kMaps)) {
92 TRACE((" kill-maps i%d\n", instr->id()));
93 KillOffset(JSObject::kMapOffset);
94 }
95 if (instr->CheckChangesFlag(kElementsKind)) {
96 TRACE((" kill-elements-kind i%d\n", instr->id()));
97 KillOffset(JSObject::kMapOffset);
98 KillOffset(JSObject::kElementsOffset);
99 }
100 if (instr->CheckChangesFlag(kElementsPointer)) {
101 TRACE((" kill-elements i%d\n", instr->id()));
102 KillOffset(JSObject::kElementsOffset);
103 }
104 if (instr->CheckChangesFlag(kOsrEntries)) {
105 TRACE((" kill-osr i%d\n", instr->id()));
106 Kill();
107 }
108 }
109 // Improvements possible:
110 // - learn from HCheckMaps for field 0
111 // - remove unobservable stores (write-after-write)
112 // - track cells
113 // - track globals
114 // - track roots
115 }
116 return this;
117 }
118
119 // Support for global analysis with HFlowEngine: Merge given state with
120 // the other incoming state.
121 static HLoadEliminationTable* Merge(HLoadEliminationTable* succ_state,
122 HBasicBlock* succ_block,
123 HLoadEliminationTable* pred_state,
124 HBasicBlock* pred_block,
125 Zone* zone) {
126 DCHECK(pred_state != NULL);
127 if (succ_state == NULL) {
128 return pred_state->Copy(succ_block, pred_block, zone);
129 } else {
130 return succ_state->Merge(succ_block, pred_state, pred_block, zone);
131 }
132 }
133
134 // Support for global analysis with HFlowEngine: Given state merged with all
135 // the other incoming states, prepare it for use.
136 static HLoadEliminationTable* Finish(HLoadEliminationTable* state,
137 HBasicBlock* block,
138 Zone* zone) {
139 DCHECK(state != NULL);
140 return state;
141 }
142
143 private:
144 // Copy state to successor block.
145 HLoadEliminationTable* Copy(HBasicBlock* succ, HBasicBlock* from_block,
146 Zone* zone) {
147 HLoadEliminationTable* copy =
148 new(zone) HLoadEliminationTable(zone, aliasing_);
149 copy->EnsureFields(fields_.length());
150 for (int i = 0; i < fields_.length(); i++) {
151 copy->fields_[i] = fields_[i] == NULL ? NULL : fields_[i]->Copy(zone);
152 }
153 if (FLAG_trace_load_elimination) {
154 TRACE((" copy-to B%d\n", succ->block_id()));
155 copy->Print();
156 }
157 return copy;
158 }
159
160 // Merge this state with the other incoming state.
161 HLoadEliminationTable* Merge(HBasicBlock* succ, HLoadEliminationTable* that,
162 HBasicBlock* that_block, Zone* zone) {
163 if (that->fields_.length() < fields_.length()) {
164 // Drop fields not in the other table.
165 fields_.Rewind(that->fields_.length());
166 }
167 for (int i = 0; i < fields_.length(); i++) {
168 // Merge the field approximations for like fields.
169 HFieldApproximation* approx = fields_[i];
170 HFieldApproximation* prev = NULL;
171 while (approx != NULL) {
172 // TODO(titzer): Merging is O(N * M); sort?
173 HFieldApproximation* other = that->Find(approx->object_, i);
174 if (other == NULL || !Equal(approx->last_value_, other->last_value_)) {
175 // Kill an entry that doesn't agree with the other value.
176 if (prev != NULL) {
177 prev->next_ = approx->next_;
178 } else {
179 fields_[i] = approx->next_;
180 }
181 approx = approx->next_;
182 continue;
183 }
184 prev = approx;
185 approx = approx->next_;
186 }
187 }
188 if (FLAG_trace_load_elimination) {
189 TRACE((" merge-to B%d\n", succ->block_id()));
190 Print();
191 }
192 return this;
193 }
194
195 friend class HLoadEliminationEffects; // Calls Kill() and others.
196 friend class HLoadEliminationPhase;
197
198 private:
199 // Process a load instruction, updating internal table state. If a previous
200 // load or store for this object and field exists, return the new value with
201 // which the load should be replaced. Otherwise, return {instr}.
202 HValue* load(HLoadNamedField* instr) {
203 // There must be no loads from non observable in-object properties.
204 DCHECK(!instr->access().IsInobject() ||
205 instr->access().existing_inobject_property());
206
207 int field = FieldOf(instr->access());
208 if (field < 0) return instr;
209
210 HValue* object = instr->object()->ActualValue();
211 HFieldApproximation* approx = FindOrCreate(object, field);
212
213 if (approx->last_value_ == NULL) {
214 // Load is not redundant. Fill out a new entry.
215 approx->last_value_ = instr;
216 return instr;
217 } else if (approx->last_value_->block()->EqualToOrDominates(
218 instr->block())) {
219 // Eliminate the load. Reuse previously stored value or load instruction.
220 return approx->last_value_;
221 } else {
222 return instr;
223 }
224 }
225
226 // Process a store instruction, updating internal table state. If a previous
227 // store to the same object and field makes this store redundant (e.g. because
228 // the stored values are the same), return NULL indicating that this store
229 // instruction is redundant. Otherwise, return {instr}.
230 HValue* store(HStoreNamedField* instr) {
231 if (instr->access().IsInobject() &&
232 !instr->access().existing_inobject_property()) {
233 TRACE((" skipping non existing property initialization store\n"));
234 return instr;
235 }
236
237 int field = FieldOf(instr->access());
238 if (field < 0) return KillIfMisaligned(instr);
239
240 HValue* object = instr->object()->ActualValue();
241 HValue* value = instr->value();
242
243 if (instr->has_transition()) {
244 // A transition introduces a new field and alters the map of the object.
245 // Since the field in the object is new, it cannot alias existing entries.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000246 KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL);
247 } else {
248 // Kill non-equivalent may-alias entries.
249 KillFieldInternal(object, field, value);
250 }
251 HFieldApproximation* approx = FindOrCreate(object, field);
252
253 if (Equal(approx->last_value_, value)) {
254 // The store is redundant because the field already has this value.
255 return NULL;
256 } else {
257 // The store is not redundant. Update the entry.
258 approx->last_value_ = value;
259 return instr;
260 }
261 }
262
263 // Kill everything in this table.
264 void Kill() {
265 fields_.Rewind(0);
266 }
267
268 // Kill all entries matching the given offset.
269 void KillOffset(int offset) {
270 int field = FieldOf(offset);
271 if (field >= 0 && field < fields_.length()) {
272 fields_[field] = NULL;
273 }
274 }
275
276 // Kill all entries aliasing the given store.
277 void KillStore(HStoreNamedField* s) {
278 int field = FieldOf(s->access());
279 if (field >= 0) {
280 KillFieldInternal(s->object()->ActualValue(), field, s->value());
281 } else {
282 KillIfMisaligned(s);
283 }
284 }
285
286 // Kill multiple entries in the case of a misaligned store.
287 HValue* KillIfMisaligned(HStoreNamedField* instr) {
288 HObjectAccess access = instr->access();
289 if (access.IsInobject()) {
290 int offset = access.offset();
291 if ((offset % kPointerSize) != 0) {
292 // Kill the field containing the first word of the access.
293 HValue* object = instr->object()->ActualValue();
294 int field = offset / kPointerSize;
295 KillFieldInternal(object, field, NULL);
296
297 // Kill the next field in case of overlap.
298 int size = access.representation().size();
299 int next_field = (offset + size - 1) / kPointerSize;
300 if (next_field != field) KillFieldInternal(object, next_field, NULL);
301 }
302 }
303 return instr;
304 }
305
306 // Find an entry for the given object and field pair.
307 HFieldApproximation* Find(HValue* object, int field) {
308 // Search for a field approximation for this object.
309 HFieldApproximation* approx = fields_[field];
310 while (approx != NULL) {
311 if (aliasing_->MustAlias(object, approx->object_)) return approx;
312 approx = approx->next_;
313 }
314 return NULL;
315 }
316
317 // Find or create an entry for the given object and field pair.
318 HFieldApproximation* FindOrCreate(HValue* object, int field) {
319 EnsureFields(field + 1);
320
321 // Search for a field approximation for this object.
322 HFieldApproximation* approx = fields_[field];
323 int count = 0;
324 while (approx != NULL) {
325 if (aliasing_->MustAlias(object, approx->object_)) return approx;
326 count++;
327 approx = approx->next_;
328 }
329
330 if (count >= kMaxTrackedObjects) {
331 // Pull the last entry off the end and repurpose it for this object.
332 approx = ReuseLastApproximation(field);
333 } else {
334 // Allocate a new entry.
335 approx = new(zone_) HFieldApproximation();
336 }
337
338 // Insert the entry at the head of the list.
339 approx->object_ = object;
340 approx->last_value_ = NULL;
341 approx->next_ = fields_[field];
342 fields_[field] = approx;
343
344 return approx;
345 }
346
347 // Kill all entries for a given field that _may_ alias the given object
348 // and do _not_ have the given value.
349 void KillFieldInternal(HValue* object, int field, HValue* value) {
350 if (field >= fields_.length()) return; // Nothing to do.
351
352 HFieldApproximation* approx = fields_[field];
353 HFieldApproximation* prev = NULL;
354 while (approx != NULL) {
355 if (aliasing_->MayAlias(object, approx->object_)) {
356 if (!Equal(approx->last_value_, value)) {
357 // Kill an aliasing entry that doesn't agree on the value.
358 if (prev != NULL) {
359 prev->next_ = approx->next_;
360 } else {
361 fields_[field] = approx->next_;
362 }
363 approx = approx->next_;
364 continue;
365 }
366 }
367 prev = approx;
368 approx = approx->next_;
369 }
370 }
371
372 bool Equal(HValue* a, HValue* b) {
373 if (a == b) return true;
374 if (a != NULL && b != NULL && a->CheckFlag(HValue::kUseGVN)) {
375 return a->Equals(b);
376 }
377 return false;
378 }
379
380 // Remove the last approximation for a field so that it can be reused.
381 // We reuse the last entry because it was the first inserted and is thus
382 // farthest away from the current instruction.
383 HFieldApproximation* ReuseLastApproximation(int field) {
384 HFieldApproximation* approx = fields_[field];
385 DCHECK(approx != NULL);
386
387 HFieldApproximation* prev = NULL;
388 while (approx->next_ != NULL) {
389 prev = approx;
390 approx = approx->next_;
391 }
392 if (prev != NULL) prev->next_ = NULL;
393 return approx;
394 }
395
396 // Compute the field index for the given object access; -1 if not tracked.
397 int FieldOf(HObjectAccess access) {
398 return access.IsInobject() ? FieldOf(access.offset()) : -1;
399 }
400
401 // Compute the field index for the given in-object offset; -1 if not tracked.
402 int FieldOf(int offset) {
403 if (offset >= kMaxTrackedFields * kPointerSize) return -1;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000404 if ((offset % kPointerSize) != 0) return -1; // Ignore misaligned accesses.
405 return offset / kPointerSize;
406 }
407
408 // Ensure internal storage for the given number of fields.
409 void EnsureFields(int num_fields) {
410 if (fields_.length() < num_fields) {
411 fields_.AddBlock(NULL, num_fields - fields_.length(), zone_);
412 }
413 }
414
415 // Print this table to stdout.
416 void Print() {
417 for (int i = 0; i < fields_.length(); i++) {
418 PrintF(" field %d: ", i);
419 for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) {
420 PrintF("[o%d =", a->object_->id());
421 if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id());
422 PrintF("] ");
423 }
424 PrintF("\n");
425 }
426 }
427
428 Zone* zone_;
429 ZoneList<HFieldApproximation*> fields_;
430 HAliasAnalyzer* aliasing_;
431};
432
433
434// Support for HFlowEngine: collect store effects within loops.
435class HLoadEliminationEffects : public ZoneObject {
436 public:
437 explicit HLoadEliminationEffects(Zone* zone)
438 : zone_(zone), stores_(5, zone) { }
439
440 inline bool Disabled() {
441 return false; // Effects are _not_ disabled.
442 }
443
444 // Process a possibly side-effecting instruction.
445 void Process(HInstruction* instr, Zone* zone) {
446 if (instr->IsStoreNamedField()) {
447 stores_.Add(HStoreNamedField::cast(instr), zone_);
448 } else {
449 flags_.Add(instr->ChangesFlags());
450 }
451 }
452
453 // Apply these effects to the given load elimination table.
454 void Apply(HLoadEliminationTable* table) {
455 // Loads must not be hoisted past the OSR entry, therefore we kill
456 // everything if we see an OSR entry.
457 if (flags_.Contains(kInobjectFields) || flags_.Contains(kOsrEntries)) {
458 table->Kill();
459 return;
460 }
461 if (flags_.Contains(kElementsKind) || flags_.Contains(kMaps)) {
462 table->KillOffset(JSObject::kMapOffset);
463 }
464 if (flags_.Contains(kElementsKind) || flags_.Contains(kElementsPointer)) {
465 table->KillOffset(JSObject::kElementsOffset);
466 }
467
468 // Kill non-agreeing fields for each store contained in these effects.
469 for (int i = 0; i < stores_.length(); i++) {
470 table->KillStore(stores_[i]);
471 }
472 }
473
474 // Union these effects with the other effects.
475 void Union(HLoadEliminationEffects* that, Zone* zone) {
476 flags_.Add(that->flags_);
477 for (int i = 0; i < that->stores_.length(); i++) {
478 stores_.Add(that->stores_[i], zone);
479 }
480 }
481
482 private:
483 Zone* zone_;
484 GVNFlagSet flags_;
485 ZoneList<HStoreNamedField*> stores_;
486};
487
488
489// The main routine of the analysis phase. Use the HFlowEngine for either a
490// local or a global analysis.
491void HLoadEliminationPhase::Run() {
492 HFlowEngine<HLoadEliminationTable, HLoadEliminationEffects>
493 engine(graph(), zone());
494 HAliasAnalyzer aliasing;
495 HLoadEliminationTable* table =
496 new(zone()) HLoadEliminationTable(zone(), &aliasing);
497
498 if (GLOBAL) {
499 // Perform a global analysis.
500 engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table);
501 } else {
502 // Perform only local analysis.
503 for (int i = 0; i < graph()->blocks()->length(); i++) {
504 table->Kill();
505 engine.AnalyzeOneBlock(graph()->blocks()->at(i), table);
506 }
507 }
508}
509
510} // namespace internal
511} // namespace v8