1 // 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-check-elimination.h"
29 #include "hydrogen-alias-analysis.h"
30 #include "hydrogen-flow-engine.h"
31
32 #define GLOBAL 1
33
34 // Only collect stats in debug mode.
35 #if DEBUG
36 #define INC_STAT(x) phase_->x++
37 #else
38 #define INC_STAT(x)
39 #endif
40
41 // For code de-uglification.
42 #define TRACE(x) if (FLAG_trace_check_elimination) PrintF x
43
44 namespace v8 {
45 namespace internal {
46
47 typedef UniqueSet<Map>* MapSet;
48
49 struct HCheckTableEntry {
50 HValue* object_; // The object being approximated. NULL => invalid entry.
51 HValue* check_; // The last check instruction.
52 MapSet maps_; // The set of known maps for the object.
53 };
54
55
56 // The main datastructure used during check elimination, which stores a
57 // set of known maps for each object.
58 class HCheckTable : public ZoneObject {
59 public:
60 static const int kMaxTrackedObjects = 10;
61
HCheckTable(HCheckEliminationPhase * phase)62 explicit HCheckTable(HCheckEliminationPhase* phase)
63 : phase_(phase),
64 cursor_(0),
65 size_(0) {
66 }
67
68 // The main processing of instructions.
Process(HInstruction * instr,Zone * zone)69 HCheckTable* Process(HInstruction* instr, Zone* zone) {
70 switch (instr->opcode()) {
71 case HValue::kCheckMaps: {
72 ReduceCheckMaps(HCheckMaps::cast(instr));
73 break;
74 }
75 case HValue::kCheckValue: {
76 ReduceCheckValue(HCheckValue::cast(instr));
77 break;
78 }
79 case HValue::kLoadNamedField: {
80 ReduceLoadNamedField(HLoadNamedField::cast(instr));
81 break;
82 }
83 case HValue::kStoreNamedField: {
84 ReduceStoreNamedField(HStoreNamedField::cast(instr));
85 break;
86 }
87 case HValue::kCompareMap: {
88 ReduceCompareMap(HCompareMap::cast(instr));
89 break;
90 }
91 case HValue::kTransitionElementsKind: {
92 ReduceTransitionElementsKind(
93 HTransitionElementsKind::cast(instr));
94 break;
95 }
96 case HValue::kCheckMapValue: {
97 ReduceCheckMapValue(HCheckMapValue::cast(instr));
98 break;
99 }
100 case HValue::kCheckHeapObject: {
101 ReduceCheckHeapObject(HCheckHeapObject::cast(instr));
102 break;
103 }
104 default: {
105 // If the instruction changes maps uncontrollably, drop everything.
106 if (instr->CheckGVNFlag(kChangesMaps) ||
107 instr->CheckGVNFlag(kChangesOsrEntries)) {
108 Kill();
109 }
110 }
111 // Improvements possible:
112 // - eliminate redundant HCheckSmi, HCheckInstanceType instructions
113 // - track which values have been HCheckHeapObject'd
114 }
115
116 return this;
117 }
118
119 // Global analysis: Copy state to successor block.
Copy(HBasicBlock * succ,Zone * zone)120 HCheckTable* Copy(HBasicBlock* succ, Zone* zone) {
121 HCheckTable* copy = new(phase_->zone()) HCheckTable(phase_);
122 for (int i = 0; i < size_; i++) {
123 HCheckTableEntry* old_entry = &entries_[i];
124 HCheckTableEntry* new_entry = ©->entries_[i];
125 // TODO(titzer): keep the check if this block dominates the successor?
126 new_entry->object_ = old_entry->object_;
127 new_entry->check_ = NULL;
128 new_entry->maps_ = old_entry->maps_->Copy(phase_->zone());
129 }
130 if (succ->predecessors()->length() == 1) {
131 HControlInstruction* end = succ->predecessors()->at(0)->end();
132 if (end->IsCompareMap() && end->SuccessorAt(0) == succ) {
133 // Learn on the true branch of if(CompareMap(x)).
134 HCompareMap* cmp = HCompareMap::cast(end);
135 HValue* object = cmp->value()->ActualValue();
136 HCheckTableEntry* entry = copy->Find(object);
137 if (entry == NULL) {
138 copy->Insert(object, cmp->map());
139 } else {
140 MapSet list = new(phase_->zone()) UniqueSet<Map>();
141 list->Add(cmp->map(), phase_->zone());
142 entry->maps_ = list;
143 }
144 }
145 // TODO(titzer): is it worthwhile to learn on false branch too?
146 }
147 return copy;
148 }
149
150 // Global analysis: Merge this state with the other incoming state.
Merge(HBasicBlock * succ,HCheckTable * that,Zone * zone)151 HCheckTable* Merge(HBasicBlock* succ, HCheckTable* that, Zone* zone) {
152 if (that->size_ == 0) {
153 // If the other state is empty, simply reset.
154 size_ = 0;
155 cursor_ = 0;
156 return this;
157 }
158 bool compact = false;
159 for (int i = 0; i < size_; i++) {
160 HCheckTableEntry* this_entry = &entries_[i];
161 HCheckTableEntry* that_entry = that->Find(this_entry->object_);
162 if (that_entry == NULL) {
163 this_entry->object_ = NULL;
164 compact = true;
165 } else {
166 this_entry->maps_ = this_entry->maps_->Union(
167 that_entry->maps_, phase_->zone());
168 if (this_entry->check_ != that_entry->check_) this_entry->check_ = NULL;
169 ASSERT(this_entry->maps_->size() > 0);
170 }
171 }
172 if (compact) Compact();
173 return this;
174 }
175
ReduceCheckMaps(HCheckMaps * instr)176 void ReduceCheckMaps(HCheckMaps* instr) {
177 HValue* object = instr->value()->ActualValue();
178 HCheckTableEntry* entry = Find(object);
179 if (entry != NULL) {
180 // entry found;
181 MapSet a = entry->maps_;
182 MapSet i = instr->map_set().Copy(phase_->zone());
183 if (a->IsSubset(i)) {
184 // The first check is more strict; the second is redundant.
185 if (entry->check_ != NULL) {
186 instr->DeleteAndReplaceWith(entry->check_);
187 INC_STAT(redundant_);
188 } else {
189 instr->DeleteAndReplaceWith(instr->value());
190 INC_STAT(removed_);
191 }
192 return;
193 }
194 i = i->Intersect(a, phase_->zone());
195 if (i->size() == 0) {
196 // Intersection is empty; probably megamorphic, which is likely to
197 // deopt anyway, so just leave things as they are.
198 INC_STAT(empty_);
199 } else {
200 // TODO(titzer): replace the first check with a more strict check
201 INC_STAT(narrowed_);
202 }
203 } else {
204 // No entry; insert a new one.
205 Insert(object, instr, instr->map_set().Copy(phase_->zone()));
206 }
207 }
208
ReduceCheckValue(HCheckValue * instr)209 void ReduceCheckValue(HCheckValue* instr) {
210 // Canonicalize HCheckValues; they might have their values load-eliminated.
211 HValue* value = instr->Canonicalize();
212 if (value == NULL) {
213 instr->DeleteAndReplaceWith(instr->value());
214 INC_STAT(removed_);
215 } else if (value != instr) {
216 instr->DeleteAndReplaceWith(value);
217 INC_STAT(redundant_);
218 }
219 }
220
ReduceLoadNamedField(HLoadNamedField * instr)221 void ReduceLoadNamedField(HLoadNamedField* instr) {
222 // Reduce a load of the map field when it is known to be a constant.
223 if (!IsMapAccess(instr->access())) return;
224
225 HValue* object = instr->object()->ActualValue();
226 MapSet maps = FindMaps(object);
227 if (maps == NULL || maps->size() != 1) return; // Not a constant.
228
229 Unique<Map> map = maps->at(0);
230 HConstant* constant = HConstant::CreateAndInsertBefore(
231 instr->block()->graph()->zone(), map, true, instr);
232 instr->DeleteAndReplaceWith(constant);
233 INC_STAT(loads_);
234 }
235
ReduceCheckMapValue(HCheckMapValue * instr)236 void ReduceCheckMapValue(HCheckMapValue* instr) {
237 if (!instr->map()->IsConstant()) return; // Nothing to learn.
238
239 HValue* object = instr->value()->ActualValue();
240 // Match a HCheckMapValue(object, HConstant(map))
241 Unique<Map> map = MapConstant(instr->map());
242 MapSet maps = FindMaps(object);
243 if (maps != NULL) {
244 if (maps->Contains(map)) {
245 if (maps->size() == 1) {
246 // Object is known to have exactly this map.
247 instr->DeleteAndReplaceWith(NULL);
248 INC_STAT(removed_);
249 } else {
250 // Only one map survives the check.
251 maps->Clear();
252 maps->Add(map, phase_->zone());
253 }
254 }
255 } else {
256 // No prior information.
257 Insert(object, map);
258 }
259 }
260
ReduceCheckHeapObject(HCheckHeapObject * instr)261 void ReduceCheckHeapObject(HCheckHeapObject* instr) {
262 if (FindMaps(instr->value()->ActualValue()) != NULL) {
263 // If the object has known maps, it's definitely a heap object.
264 instr->DeleteAndReplaceWith(instr->value());
265 INC_STAT(removed_cho_);
266 }
267 }
268
ReduceStoreNamedField(HStoreNamedField * instr)269 void ReduceStoreNamedField(HStoreNamedField* instr) {
270 HValue* object = instr->object()->ActualValue();
271 if (instr->has_transition()) {
272 // This store transitions the object to a new map.
273 Kill(object);
274 Insert(object, MapConstant(instr->transition()));
275 } else if (IsMapAccess(instr->access())) {
276 // This is a store directly to the map field of the object.
277 Kill(object);
278 if (!instr->value()->IsConstant()) return;
279 Insert(object, MapConstant(instr->value()));
280 } else {
281 // If the instruction changes maps, it should be handled above.
282 CHECK(!instr->CheckGVNFlag(kChangesMaps));
283 }
284 }
285
ReduceCompareMap(HCompareMap * instr)286 void ReduceCompareMap(HCompareMap* instr) {
287 MapSet maps = FindMaps(instr->value()->ActualValue());
288 if (maps == NULL) return;
289 if (maps->Contains(instr->map())) {
290 if (maps->size() == 1) {
291 // TODO(titzer): replace with goto true branch
292 INC_STAT(compares_true_);
293 }
294 } else {
295 // TODO(titzer): replace with goto false branch
296 INC_STAT(compares_false_);
297 }
298 }
299
ReduceTransitionElementsKind(HTransitionElementsKind * instr)300 void ReduceTransitionElementsKind(HTransitionElementsKind* instr) {
301 MapSet maps = FindMaps(instr->object()->ActualValue());
302 // Can only learn more about an object that already has a known set of maps.
303 if (maps == NULL) return;
304 if (maps->Contains(instr->original_map())) {
305 // If the object has the original map, it will be transitioned.
306 maps->Remove(instr->original_map());
307 maps->Add(instr->transitioned_map(), phase_->zone());
308 } else {
309 // Object does not have the given map, thus the transition is redundant.
310 instr->DeleteAndReplaceWith(instr->object());
311 INC_STAT(transitions_);
312 }
313 }
314
315 // Kill everything in the table.
Kill()316 void Kill() {
317 size_ = 0;
318 cursor_ = 0;
319 }
320
321 // Kill everything in the table that may alias {object}.
Kill(HValue * object)322 void Kill(HValue* object) {
323 bool compact = false;
324 for (int i = 0; i < size_; i++) {
325 HCheckTableEntry* entry = &entries_[i];
326 ASSERT(entry->object_ != NULL);
327 if (phase_->aliasing_->MayAlias(entry->object_, object)) {
328 entry->object_ = NULL;
329 compact = true;
330 }
331 }
332 if (compact) Compact();
333 ASSERT(Find(object) == NULL);
334 }
335
Compact()336 void Compact() {
337 // First, compact the array in place.
338 int max = size_, dest = 0, old_cursor = cursor_;
339 for (int i = 0; i < max; i++) {
340 if (entries_[i].object_ != NULL) {
341 if (dest != i) entries_[dest] = entries_[i];
342 dest++;
343 } else {
344 if (i < old_cursor) cursor_--;
345 size_--;
346 }
347 }
348 ASSERT(size_ == dest);
349 ASSERT(cursor_ <= size_);
350
351 // Preserve the age of the entries by moving the older entries to the end.
352 if (cursor_ == size_) return; // Cursor already points at end.
353 if (cursor_ != 0) {
354 // | L = oldest | R = newest | |
355 // ^ cursor ^ size ^ MAX
356 HCheckTableEntry tmp_entries[kMaxTrackedObjects];
357 int L = cursor_;
358 int R = size_ - cursor_;
359
360 OS::MemMove(&tmp_entries[0], &entries_[0], L * sizeof(HCheckTableEntry));
361 OS::MemMove(&entries_[0], &entries_[L], R * sizeof(HCheckTableEntry));
362 OS::MemMove(&entries_[R], &tmp_entries[0], L * sizeof(HCheckTableEntry));
363 }
364
365 cursor_ = size_; // Move cursor to end.
366 }
367
Print()368 void Print() {
369 for (int i = 0; i < size_; i++) {
370 HCheckTableEntry* entry = &entries_[i];
371 ASSERT(entry->object_ != NULL);
372 PrintF(" checkmaps-table @%d: object #%d ", i, entry->object_->id());
373 if (entry->check_ != NULL) {
374 PrintF("check #%d ", entry->check_->id());
375 }
376 MapSet list = entry->maps_;
377 PrintF("%d maps { ", list->size());
378 for (int j = 0; j < list->size(); j++) {
379 if (j > 0) PrintF(", ");
380 PrintF("%" V8PRIxPTR, list->at(j).Hashcode());
381 }
382 PrintF(" }\n");
383 }
384 }
385
386 private:
Find(HValue * object)387 HCheckTableEntry* Find(HValue* object) {
388 for (int i = size_ - 1; i >= 0; i--) {
389 // Search from most-recently-inserted to least-recently-inserted.
390 HCheckTableEntry* entry = &entries_[i];
391 ASSERT(entry->object_ != NULL);
392 if (phase_->aliasing_->MustAlias(entry->object_, object)) return entry;
393 }
394 return NULL;
395 }
396
FindMaps(HValue * object)397 MapSet FindMaps(HValue* object) {
398 HCheckTableEntry* entry = Find(object);
399 return entry == NULL ? NULL : entry->maps_;
400 }
401
Insert(HValue * object,Unique<Map> map)402 void Insert(HValue* object, Unique<Map> map) {
403 MapSet list = new(phase_->zone()) UniqueSet<Map>();
404 list->Add(map, phase_->zone());
405 Insert(object, NULL, list);
406 }
407
Insert(HValue * object,HCheckMaps * check,MapSet maps)408 void Insert(HValue* object, HCheckMaps* check, MapSet maps) {
409 HCheckTableEntry* entry = &entries_[cursor_++];
410 entry->object_ = object;
411 entry->check_ = check;
412 entry->maps_ = maps;
413 // If the table becomes full, wrap around and overwrite older entries.
414 if (cursor_ == kMaxTrackedObjects) cursor_ = 0;
415 if (size_ < kMaxTrackedObjects) size_++;
416 }
417
IsMapAccess(HObjectAccess access)418 bool IsMapAccess(HObjectAccess access) {
419 return access.IsInobject() && access.offset() == JSObject::kMapOffset;
420 }
421
MapConstant(HValue * value)422 Unique<Map> MapConstant(HValue* value) {
423 return Unique<Map>::cast(HConstant::cast(value)->GetUnique());
424 }
425
426 friend class HCheckMapsEffects;
427
428 HCheckEliminationPhase* phase_;
429 HCheckTableEntry entries_[kMaxTrackedObjects];
430 int16_t cursor_; // Must be <= kMaxTrackedObjects
431 int16_t size_; // Must be <= kMaxTrackedObjects
432 // TODO(titzer): STATIC_ASSERT kMaxTrackedObjects < max(cursor_)
433 };
434
435
436 // Collects instructions that can cause effects that invalidate information
437 // needed for check elimination.
438 class HCheckMapsEffects : public ZoneObject {
439 public:
HCheckMapsEffects(Zone * zone)440 explicit HCheckMapsEffects(Zone* zone)
441 : maps_stored_(false),
442 stores_(5, zone) { }
443
Disabled()444 inline bool Disabled() {
445 return false; // Effects are _not_ disabled.
446 }
447
448 // Process a possibly side-effecting instruction.
Process(HInstruction * instr,Zone * zone)449 void Process(HInstruction* instr, Zone* zone) {
450 switch (instr->opcode()) {
451 case HValue::kStoreNamedField: {
452 stores_.Add(HStoreNamedField::cast(instr), zone);
453 break;
454 }
455 case HValue::kOsrEntry: {
456 // Kill everything. Loads must not be hoisted past the OSR entry.
457 maps_stored_ = true;
458 }
459 default: {
460 maps_stored_ |= (instr->CheckGVNFlag(kChangesMaps) |
461 instr->CheckGVNFlag(kChangesElementsKind));
462 }
463 }
464 }
465
466 // Apply these effects to the given check elimination table.
Apply(HCheckTable * table)467 void Apply(HCheckTable* table) {
468 if (maps_stored_) {
469 // Uncontrollable map modifications; kill everything.
470 table->Kill();
471 return;
472 }
473
474 // Kill maps for each store contained in these effects.
475 for (int i = 0; i < stores_.length(); i++) {
476 HStoreNamedField* s = stores_[i];
477 if (table->IsMapAccess(s->access()) || s->has_transition()) {
478 table->Kill(s->object()->ActualValue());
479 }
480 }
481 }
482
483 // Union these effects with the other effects.
Union(HCheckMapsEffects * that,Zone * zone)484 void Union(HCheckMapsEffects* that, Zone* zone) {
485 maps_stored_ |= that->maps_stored_;
486 for (int i = 0; i < that->stores_.length(); i++) {
487 stores_.Add(that->stores_[i], zone);
488 }
489 }
490
491 private:
492 bool maps_stored_ : 1;
493 ZoneList<HStoreNamedField*> stores_;
494 };
495
496
497 // The main routine of the analysis phase. Use the HFlowEngine for either a
498 // local or a global analysis.
Run()499 void HCheckEliminationPhase::Run() {
500 HFlowEngine<HCheckTable, HCheckMapsEffects> engine(graph(), zone());
501 HCheckTable* table = new(zone()) HCheckTable(this);
502
503 if (GLOBAL) {
504 // Perform a global analysis.
505 engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table);
506 } else {
507 // Perform only local analysis.
508 for (int i = 0; i < graph()->blocks()->length(); i++) {
509 table->Kill();
510 engine.AnalyzeOneBlock(graph()->blocks()->at(i), table);
511 }
512 }
513
514 if (FLAG_trace_check_elimination) PrintStats();
515 }
516
517
518 // Are we eliminated yet?
PrintStats()519 void HCheckEliminationPhase::PrintStats() {
520 #if DEBUG
521 #define PRINT_STAT(x) if (x##_ > 0) PrintF(" %-16s = %2d\n", #x, x##_)
522 #else
523 #define PRINT_STAT(x)
524 #endif
525 PRINT_STAT(redundant);
526 PRINT_STAT(removed);
527 PRINT_STAT(removed_cho);
528 PRINT_STAT(narrowed);
529 PRINT_STAT(loads);
530 PRINT_STAT(empty);
531 PRINT_STAT(compares_true);
532 PRINT_STAT(compares_false);
533 PRINT_STAT(transitions);
534 }
535
536 } } // namespace v8::internal
537