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
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.
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.
28 #include "hydrogen-alias-analysis.h"
29 #include "hydrogen-load-elimination.h"
30 #include "hydrogen-instructions.h"
31 #include "hydrogen-flow-engine.h"
37 #define TRACE(x) if (FLAG_trace_load_elimination) PrintF x
39 static const int kMaxTrackedFields = 16;
40 static const int kMaxTrackedObjects = 5;
42 // An element in the field approximation list.
43 class HFieldApproximation : public ZoneObject {
44 public: // Just a data blob.
47 HFieldApproximation* next_;
49 // Recursively copy the entire linked list of field approximations.
50 HFieldApproximation* Copy(Zone* zone) {
51 if (this == NULL) return NULL;
52 HFieldApproximation* copy = new(zone) HFieldApproximation();
53 copy->object_ = this->object_;
54 copy->last_value_ = this->last_value_;
55 copy->next_ = this->next_->Copy(zone);
61 // The main datastructure used during load/store elimination. Each in-object
62 // field is tracked separately. For each field, store a list of known field
63 // values for known objects.
64 class HLoadEliminationTable : public ZoneObject {
66 HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing)
67 : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { }
69 // The main processing of instructions.
70 HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) {
71 switch (instr->opcode()) {
72 case HValue::kLoadNamedField: {
73 HLoadNamedField* l = HLoadNamedField::cast(instr);
74 TRACE((" process L%d field %d (o%d)\n",
77 l->object()->ActualValue()->id()));
78 HValue* result = load(l);
79 if (result != instr &&
80 result->type().Equals(instr->type()) &&
81 result->representation().Equals(instr->representation())) {
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);
88 case HValue::kStoreNamedField: {
89 HStoreNamedField* s = HStoreNamedField::cast(instr);
90 TRACE((" process S%d field %d (o%d) = v%d\n",
93 s->object()->ActualValue()->id(),
95 HValue* result = store(s);
97 // The store is redundant. Remove it.
98 TRACE((" remove S%d\n", instr->id()));
99 instr->DeleteAndReplaceWith(NULL);
104 if (instr->CheckGVNFlag(kChangesInobjectFields)) {
105 TRACE((" kill-all i%d\n", instr->id()));
109 if (instr->CheckGVNFlag(kChangesMaps)) {
110 TRACE((" kill-maps i%d\n", instr->id()));
111 KillOffset(JSObject::kMapOffset);
113 if (instr->CheckGVNFlag(kChangesElementsKind)) {
114 TRACE((" kill-elements-kind i%d\n", instr->id()));
115 KillOffset(JSObject::kMapOffset);
116 KillOffset(JSObject::kElementsOffset);
118 if (instr->CheckGVNFlag(kChangesElementsPointer)) {
119 TRACE((" kill-elements i%d\n", instr->id()));
120 KillOffset(JSObject::kElementsOffset);
122 if (instr->CheckGVNFlag(kChangesOsrEntries)) {
123 TRACE((" kill-osr i%d\n", instr->id()));
127 // Improvements possible:
128 // - learn from HCheckMaps for field 0
129 // - remove unobservable stores (write-after-write)
137 // Support for global analysis with HFlowEngine: Copy state to successor
139 HLoadEliminationTable* Copy(HBasicBlock* succ, HBasicBlock* from_block,
141 HLoadEliminationTable* copy =
142 new(zone) HLoadEliminationTable(zone, aliasing_);
143 copy->EnsureFields(fields_.length());
144 for (int i = 0; i < fields_.length(); i++) {
145 copy->fields_[i] = fields_[i]->Copy(zone);
147 if (FLAG_trace_load_elimination) {
148 TRACE((" copy-to B%d\n", succ->block_id()));
154 // Support for global analysis with HFlowEngine: Merge this state with
155 // the other incoming state.
156 HLoadEliminationTable* Merge(HBasicBlock* succ, HLoadEliminationTable* that,
157 HBasicBlock* that_block, Zone* zone) {
158 if (that->fields_.length() < fields_.length()) {
159 // Drop fields not in the other table.
160 fields_.Rewind(that->fields_.length());
162 for (int i = 0; i < fields_.length(); i++) {
163 // Merge the field approximations for like fields.
164 HFieldApproximation* approx = fields_[i];
165 HFieldApproximation* prev = NULL;
166 while (approx != NULL) {
167 // TODO(titzer): Merging is O(N * M); sort?
168 HFieldApproximation* other = that->Find(approx->object_, i);
169 if (other == NULL || !Equal(approx->last_value_, other->last_value_)) {
170 // Kill an entry that doesn't agree with the other value.
172 prev->next_ = approx->next_;
174 fields_[i] = approx->next_;
176 approx = approx->next_;
180 approx = approx->next_;
183 if (FLAG_trace_load_elimination) {
184 TRACE((" merge-to B%d\n", succ->block_id()));
190 friend class HLoadEliminationEffects; // Calls Kill() and others.
191 friend class HLoadEliminationPhase;
194 // Process a load instruction, updating internal table state. If a previous
195 // load or store for this object and field exists, return the new value with
196 // which the load should be replaced. Otherwise, return {instr}.
197 HValue* load(HLoadNamedField* instr) {
198 // There must be no loads from non observable in-object properties.
199 ASSERT(!instr->access().IsInobject() ||
200 instr->access().existing_inobject_property());
202 int field = FieldOf(instr->access());
203 if (field < 0) return instr;
205 HValue* object = instr->object()->ActualValue();
206 HFieldApproximation* approx = FindOrCreate(object, field);
208 if (approx->last_value_ == NULL) {
209 // Load is not redundant. Fill out a new entry.
210 approx->last_value_ = instr;
212 } else if (approx->last_value_->block()->EqualToOrDominates(
214 // Eliminate the load. Reuse previously stored value or load instruction.
215 return approx->last_value_;
221 // Process a store instruction, updating internal table state. If a previous
222 // store to the same object and field makes this store redundant (e.g. because
223 // the stored values are the same), return NULL indicating that this store
224 // instruction is redundant. Otherwise, return {instr}.
225 HValue* store(HStoreNamedField* instr) {
226 if (instr->access().IsInobject() &&
227 !instr->access().existing_inobject_property()) {
228 TRACE((" skipping non existing property initialization store\n"));
232 int field = FieldOf(instr->access());
233 if (field < 0) return KillIfMisaligned(instr);
235 HValue* object = instr->object()->ActualValue();
236 HValue* value = instr->value();
238 if (instr->has_transition()) {
239 // A transition introduces a new field and alters the map of the object.
240 // Since the field in the object is new, it cannot alias existing entries.
241 // TODO(titzer): introduce a constant for the new map and remember it.
242 KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL);
244 // Kill non-equivalent may-alias entries.
245 KillFieldInternal(object, field, value);
247 HFieldApproximation* approx = FindOrCreate(object, field);
249 if (Equal(approx->last_value_, value)) {
250 // The store is redundant because the field already has this value.
253 // The store is not redundant. Update the entry.
254 approx->last_value_ = value;
259 // Kill everything in this table.
264 // Kill all entries matching the given offset.
265 void KillOffset(int offset) {
266 int field = FieldOf(offset);
267 if (field >= 0 && field < fields_.length()) {
268 fields_[field] = NULL;
272 // Kill all entries aliasing the given store.
273 void KillStore(HStoreNamedField* s) {
274 int field = FieldOf(s->access());
276 KillFieldInternal(s->object()->ActualValue(), field, s->value());
282 // Kill multiple entries in the case of a misaligned store.
283 HValue* KillIfMisaligned(HStoreNamedField* instr) {
284 HObjectAccess access = instr->access();
285 if (access.IsInobject()) {
286 int offset = access.offset();
287 if ((offset % kPointerSize) != 0) {
288 // Kill the field containing the first word of the access.
289 HValue* object = instr->object()->ActualValue();
290 int field = offset / kPointerSize;
291 KillFieldInternal(object, field, NULL);
293 // Kill the next field in case of overlap.
294 int size = access.representation().size();
295 int next_field = (offset + size - 1) / kPointerSize;
296 if (next_field != field) KillFieldInternal(object, next_field, NULL);
302 // Find an entry for the given object and field pair.
303 HFieldApproximation* Find(HValue* object, int field) {
304 // Search for a field approximation for this object.
305 HFieldApproximation* approx = fields_[field];
306 while (approx != NULL) {
307 if (aliasing_->MustAlias(object, approx->object_)) return approx;
308 approx = approx->next_;
313 // Find or create an entry for the given object and field pair.
314 HFieldApproximation* FindOrCreate(HValue* object, int field) {
315 EnsureFields(field + 1);
317 // Search for a field approximation for this object.
318 HFieldApproximation* approx = fields_[field];
320 while (approx != NULL) {
321 if (aliasing_->MustAlias(object, approx->object_)) return approx;
323 approx = approx->next_;
326 if (count >= kMaxTrackedObjects) {
327 // Pull the last entry off the end and repurpose it for this object.
328 approx = ReuseLastApproximation(field);
330 // Allocate a new entry.
331 approx = new(zone_) HFieldApproximation();
334 // Insert the entry at the head of the list.
335 approx->object_ = object;
336 approx->last_value_ = NULL;
337 approx->next_ = fields_[field];
338 fields_[field] = approx;
343 // Kill all entries for a given field that _may_ alias the given object
344 // and do _not_ have the given value.
345 void KillFieldInternal(HValue* object, int field, HValue* value) {
346 if (field >= fields_.length()) return; // Nothing to do.
348 HFieldApproximation* approx = fields_[field];
349 HFieldApproximation* prev = NULL;
350 while (approx != NULL) {
351 if (aliasing_->MayAlias(object, approx->object_)) {
352 if (!Equal(approx->last_value_, value)) {
353 // Kill an aliasing entry that doesn't agree on the value.
355 prev->next_ = approx->next_;
357 fields_[field] = approx->next_;
359 approx = approx->next_;
364 approx = approx->next_;
368 bool Equal(HValue* a, HValue* b) {
369 if (a == b) return true;
370 if (a != NULL && b != NULL && a->CheckFlag(HValue::kUseGVN)) {
376 // Remove the last approximation for a field so that it can be reused.
377 // We reuse the last entry because it was the first inserted and is thus
378 // farthest away from the current instruction.
379 HFieldApproximation* ReuseLastApproximation(int field) {
380 HFieldApproximation* approx = fields_[field];
381 ASSERT(approx != NULL);
383 HFieldApproximation* prev = NULL;
384 while (approx->next_ != NULL) {
386 approx = approx->next_;
388 if (prev != NULL) prev->next_ = NULL;
392 // Compute the field index for the given object access; -1 if not tracked.
393 int FieldOf(HObjectAccess access) {
394 return access.IsInobject() ? FieldOf(access.offset()) : -1;
397 // Compute the field index for the given in-object offset; -1 if not tracked.
398 int FieldOf(int offset) {
399 if (offset >= kMaxTrackedFields * kPointerSize) return -1;
400 // TODO(titzer): track misaligned loads in a separate list?
401 if ((offset % kPointerSize) != 0) return -1; // Ignore misaligned accesses.
402 return offset / kPointerSize;
405 // Ensure internal storage for the given number of fields.
406 void EnsureFields(int num_fields) {
407 if (fields_.length() < num_fields) {
408 fields_.AddBlock(NULL, num_fields - fields_.length(), zone_);
412 // Print this table to stdout.
414 for (int i = 0; i < fields_.length(); i++) {
415 PrintF(" field %d: ", i);
416 for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) {
417 PrintF("[o%d =", a->object_->id());
418 if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id());
426 ZoneList<HFieldApproximation*> fields_;
427 HAliasAnalyzer* aliasing_;
431 // Support for HFlowEngine: collect store effects within loops.
432 class HLoadEliminationEffects : public ZoneObject {
434 explicit HLoadEliminationEffects(Zone* zone)
437 fields_stored_(false),
438 elements_stored_(false),
441 inline bool Disabled() {
442 return false; // Effects are _not_ disabled.
445 // Process a possibly side-effecting instruction.
446 void Process(HInstruction* instr, Zone* zone) {
447 switch (instr->opcode()) {
448 case HValue::kStoreNamedField: {
449 stores_.Add(HStoreNamedField::cast(instr), zone_);
452 case HValue::kOsrEntry: {
453 // Kill everything. Loads must not be hoisted past the OSR entry.
455 fields_stored_ = true;
456 elements_stored_ = true;
459 fields_stored_ |= instr->CheckGVNFlag(kChangesInobjectFields);
460 maps_stored_ |= instr->CheckGVNFlag(kChangesMaps);
461 maps_stored_ |= instr->CheckGVNFlag(kChangesElementsKind);
462 elements_stored_ |= instr->CheckGVNFlag(kChangesElementsKind);
463 elements_stored_ |= instr->CheckGVNFlag(kChangesElementsPointer);
468 // Apply these effects to the given load elimination table.
469 void Apply(HLoadEliminationTable* table) {
470 if (fields_stored_) {
475 table->KillOffset(JSObject::kMapOffset);
477 if (elements_stored_) {
478 table->KillOffset(JSObject::kElementsOffset);
481 // Kill non-agreeing fields for each store contained in these effects.
482 for (int i = 0; i < stores_.length(); i++) {
483 table->KillStore(stores_[i]);
487 // Union these effects with the other effects.
488 void Union(HLoadEliminationEffects* that, Zone* zone) {
489 maps_stored_ |= that->maps_stored_;
490 fields_stored_ |= that->fields_stored_;
491 elements_stored_ |= that->elements_stored_;
492 for (int i = 0; i < that->stores_.length(); i++) {
493 stores_.Add(that->stores_[i], zone);
499 bool maps_stored_ : 1;
500 bool fields_stored_ : 1;
501 bool elements_stored_ : 1;
502 ZoneList<HStoreNamedField*> stores_;
506 // The main routine of the analysis phase. Use the HFlowEngine for either a
507 // local or a global analysis.
508 void HLoadEliminationPhase::Run() {
509 HFlowEngine<HLoadEliminationTable, HLoadEliminationEffects>
510 engine(graph(), zone());
511 HAliasAnalyzer aliasing;
512 HLoadEliminationTable* table =
513 new(zone()) HLoadEliminationTable(zone(), &aliasing);
516 // Perform a global analysis.
517 engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table);
519 // Perform only local analysis.
520 for (int i = 0; i < graph()->blocks()->length(); i++) {
522 engine.AnalyzeOneBlock(graph()->blocks()->at(i), table);
527 } } // namespace v8::internal