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