hydrogen-load-elimination.cc 18.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "hydrogen-alias-analysis.h"
#include "hydrogen-load-elimination.h"
#include "hydrogen-instructions.h"
31
#include "hydrogen-flow-engine.h"
32 33 34 35

namespace v8 {
namespace internal {

36 37 38
#define GLOBAL true
#define TRACE(x) if (FLAG_trace_load_elimination) PrintF x

39 40 41 42 43 44 45 46 47
static const int kMaxTrackedFields = 16;
static const int kMaxTrackedObjects = 5;

// An element in the field approximation list.
class HFieldApproximation : public ZoneObject {
 public:  // Just a data blob.
  HValue* object_;
  HValue* last_value_;
  HFieldApproximation* next_;
48 49 50 51 52 53 54 55 56 57

  // Recursively copy the entire linked list of field approximations.
  HFieldApproximation* Copy(Zone* zone) {
    if (this == NULL) return NULL;
    HFieldApproximation* copy = new(zone) HFieldApproximation();
    copy->object_ = this->object_;
    copy->last_value_ = this->last_value_;
    copy->next_ = this->next_->Copy(zone);
    return copy;
  }
58 59 60 61 62 63
};


// The main datastructure used during load/store elimination. Each in-object
// field is tracked separately. For each field, store a list of known field
// values for known objects.
64
class HLoadEliminationTable : public ZoneObject {
65 66 67 68
 public:
  HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing)
    : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { }

69 70 71 72 73 74 75 76 77 78
  // The main processing of instructions.
  HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) {
    switch (instr->opcode()) {
      case HValue::kLoadNamedField: {
        HLoadNamedField* l = HLoadNamedField::cast(instr);
        TRACE((" process L%d field %d (o%d)\n",
               instr->id(),
               FieldOf(l->access()),
               l->object()->ActualValue()->id()));
        HValue* result = load(l);
79 80 81
        if (result != instr &&
            result->type().Equals(instr->type()) &&
            result->representation().Equals(instr->representation())) {
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
          // The load can be replaced with a previous load or a value.
          TRACE(("  replace L%d -> v%d\n", instr->id(), result->id()));
          instr->DeleteAndReplaceWith(result);
        }
        break;
      }
      case HValue::kStoreNamedField: {
        HStoreNamedField* s = HStoreNamedField::cast(instr);
        TRACE((" process S%d field %d (o%d) = v%d\n",
               instr->id(),
               FieldOf(s->access()),
               s->object()->ActualValue()->id(),
               s->value()->id()));
        HValue* result = store(s);
        if (result == NULL) {
          // The store is redundant. Remove it.
          TRACE(("  remove S%d\n", instr->id()));
          instr->DeleteAndReplaceWith(NULL);
        }
        break;
      }
103 104 105 106 107 108 109
      case HValue::kTransitionElementsKind: {
        HTransitionElementsKind* t = HTransitionElementsKind::cast(instr);
        HValue* object = t->object()->ActualValue();
        KillFieldInternal(object, FieldOf(JSArray::kElementsOffset), NULL);
        KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL);
        break;
      }
110
      default: {
111
        if (instr->CheckChangesFlag(kInobjectFields)) {
112 113 114 115
          TRACE((" kill-all i%d\n", instr->id()));
          Kill();
          break;
        }
116
        if (instr->CheckChangesFlag(kMaps)) {
117 118 119
          TRACE((" kill-maps i%d\n", instr->id()));
          KillOffset(JSObject::kMapOffset);
        }
120
        if (instr->CheckChangesFlag(kElementsKind)) {
121 122 123 124
          TRACE((" kill-elements-kind i%d\n", instr->id()));
          KillOffset(JSObject::kMapOffset);
          KillOffset(JSObject::kElementsOffset);
        }
125
        if (instr->CheckChangesFlag(kElementsPointer)) {
126 127 128
          TRACE((" kill-elements i%d\n", instr->id()));
          KillOffset(JSObject::kElementsOffset);
        }
129
        if (instr->CheckChangesFlag(kOsrEntries)) {
130 131 132 133 134 135 136 137 138 139 140 141 142 143
          TRACE((" kill-osr i%d\n", instr->id()));
          Kill();
        }
      }
      // Improvements possible:
      // - learn from HCheckMaps for field 0
      // - remove unobservable stores (write-after-write)
      // - track cells
      // - track globals
      // - track roots
    }
    return this;
  }

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
  // Support for global analysis with HFlowEngine: Merge given state with
  // the other incoming state.
  static HLoadEliminationTable* Merge(HLoadEliminationTable* succ_state,
                                      HBasicBlock* succ_block,
                                      HLoadEliminationTable* pred_state,
                                      HBasicBlock* pred_block,
                                      Zone* zone) {
    ASSERT(pred_state != NULL);
    if (succ_state == NULL) {
      return pred_state->Copy(succ_block, pred_block, zone);
    } else {
      return succ_state->Merge(succ_block, pred_state, pred_block, zone);
    }
  }

  // Support for global analysis with HFlowEngine: Given state merged with all
  // the other incoming states, prepare it for use.
  static HLoadEliminationTable* Finish(HLoadEliminationTable* state,
                                       HBasicBlock* block,
                                       Zone* zone) {
    ASSERT(state != NULL);
    return state;
  }

 private:
  // Copy state to successor block.
170 171
  HLoadEliminationTable* Copy(HBasicBlock* succ, HBasicBlock* from_block,
                              Zone* zone) {
172 173 174 175 176 177 178 179 180 181 182 183 184
    HLoadEliminationTable* copy =
        new(zone) HLoadEliminationTable(zone, aliasing_);
    copy->EnsureFields(fields_.length());
    for (int i = 0; i < fields_.length(); i++) {
      copy->fields_[i] = fields_[i]->Copy(zone);
    }
    if (FLAG_trace_load_elimination) {
      TRACE((" copy-to B%d\n", succ->block_id()));
      copy->Print();
    }
    return copy;
  }

185
  // Merge this state with the other incoming state.
186 187
  HLoadEliminationTable* Merge(HBasicBlock* succ, HLoadEliminationTable* that,
                               HBasicBlock* that_block, Zone* zone) {
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
    if (that->fields_.length() < fields_.length()) {
      // Drop fields not in the other table.
      fields_.Rewind(that->fields_.length());
    }
    for (int i = 0; i < fields_.length(); i++) {
      // Merge the field approximations for like fields.
      HFieldApproximation* approx = fields_[i];
      HFieldApproximation* prev = NULL;
      while (approx != NULL) {
        // TODO(titzer): Merging is O(N * M); sort?
        HFieldApproximation* other = that->Find(approx->object_, i);
        if (other == NULL || !Equal(approx->last_value_, other->last_value_)) {
          // Kill an entry that doesn't agree with the other value.
          if (prev != NULL) {
            prev->next_ = approx->next_;
          } else {
            fields_[i] = approx->next_;
          }
          approx = approx->next_;
          continue;
        }
        prev = approx;
        approx = approx->next_;
      }
    }
213 214 215 216
    if (FLAG_trace_load_elimination) {
      TRACE((" merge-to B%d\n", succ->block_id()));
      Print();
    }
217 218 219 220 221 222 223
    return this;
  }

  friend class HLoadEliminationEffects;  // Calls Kill() and others.
  friend class HLoadEliminationPhase;

 private:
224 225 226 227
  // Process a load instruction, updating internal table state. If a previous
  // load or store for this object and field exists, return the new value with
  // which the load should be replaced. Otherwise, return {instr}.
  HValue* load(HLoadNamedField* instr) {
228 229 230 231
    // There must be no loads from non observable in-object properties.
    ASSERT(!instr->access().IsInobject() ||
           instr->access().existing_inobject_property());

232 233 234 235 236 237 238 239 240 241
    int field = FieldOf(instr->access());
    if (field < 0) return instr;

    HValue* object = instr->object()->ActualValue();
    HFieldApproximation* approx = FindOrCreate(object, field);

    if (approx->last_value_ == NULL) {
      // Load is not redundant. Fill out a new entry.
      approx->last_value_ = instr;
      return instr;
242 243
    } else if (approx->last_value_->block()->EqualToOrDominates(
        instr->block())) {
244 245
      // Eliminate the load. Reuse previously stored value or load instruction.
      return approx->last_value_;
246 247
    } else {
      return instr;
248 249 250 251 252 253 254 255
    }
  }

  // Process a store instruction, updating internal table state. If a previous
  // store to the same object and field makes this store redundant (e.g. because
  // the stored values are the same), return NULL indicating that this store
  // instruction is redundant. Otherwise, return {instr}.
  HValue* store(HStoreNamedField* instr) {
256 257 258
    if (instr->access().IsInobject() &&
        !instr->access().existing_inobject_property()) {
      TRACE(("  skipping non existing property initialization store\n"));
259 260 261
      return instr;
    }

262
    int field = FieldOf(instr->access());
263
    if (field < 0) return KillIfMisaligned(instr);
264 265 266 267 268

    HValue* object = instr->object()->ActualValue();
    HValue* value = instr->value();

    if (instr->has_transition()) {
269 270 271
      // A transition introduces a new field and alters the map of the object.
      // Since the field in the object is new, it cannot alias existing entries.
      // TODO(titzer): introduce a constant for the new map and remember it.
272
      KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL);
273 274 275
    } else {
      // Kill non-equivalent may-alias entries.
      KillFieldInternal(object, field, value);
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
    }
    HFieldApproximation* approx = FindOrCreate(object, field);

    if (Equal(approx->last_value_, value)) {
      // The store is redundant because the field already has this value.
      return NULL;
    } else {
      // The store is not redundant. Update the entry.
      approx->last_value_ = value;
      return instr;
    }
  }

  // Kill everything in this table.
  void Kill() {
    fields_.Rewind(0);
  }

  // Kill all entries matching the given offset.
  void KillOffset(int offset) {
    int field = FieldOf(offset);
    if (field >= 0 && field < fields_.length()) {
      fields_[field] = NULL;
    }
  }

302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
  // Kill all entries aliasing the given store.
  void KillStore(HStoreNamedField* s) {
    int field = FieldOf(s->access());
    if (field >= 0) {
      KillFieldInternal(s->object()->ActualValue(), field, s->value());
    } else {
      KillIfMisaligned(s);
    }
  }

  // Kill multiple entries in the case of a misaligned store.
  HValue* KillIfMisaligned(HStoreNamedField* instr) {
    HObjectAccess access = instr->access();
    if (access.IsInobject()) {
      int offset = access.offset();
      if ((offset % kPointerSize) != 0) {
        // Kill the field containing the first word of the access.
        HValue* object = instr->object()->ActualValue();
        int field = offset / kPointerSize;
        KillFieldInternal(object, field, NULL);

        // Kill the next field in case of overlap.
324
        int size = access.representation().size();
325 326 327 328 329 330 331
        int next_field = (offset + size - 1) / kPointerSize;
        if (next_field != field) KillFieldInternal(object, next_field, NULL);
      }
    }
    return instr;
  }

332 333 334 335 336 337 338
  // Find an entry for the given object and field pair.
  HFieldApproximation* Find(HValue* object, int field) {
    // Search for a field approximation for this object.
    HFieldApproximation* approx = fields_[field];
    while (approx != NULL) {
      if (aliasing_->MustAlias(object, approx->object_)) return approx;
      approx = approx->next_;
339
    }
340
    return NULL;
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
  }

  // Find or create an entry for the given object and field pair.
  HFieldApproximation* FindOrCreate(HValue* object, int field) {
    EnsureFields(field + 1);

    // Search for a field approximation for this object.
    HFieldApproximation* approx = fields_[field];
    int count = 0;
    while (approx != NULL) {
      if (aliasing_->MustAlias(object, approx->object_)) return approx;
      count++;
      approx = approx->next_;
    }

    if (count >= kMaxTrackedObjects) {
      // Pull the last entry off the end and repurpose it for this object.
      approx = ReuseLastApproximation(field);
    } else {
      // Allocate a new entry.
      approx = new(zone_) HFieldApproximation();
    }

    // Insert the entry at the head of the list.
    approx->object_ = object;
    approx->last_value_ = NULL;
    approx->next_ = fields_[field];
    fields_[field] = approx;

    return approx;
  }

  // Kill all entries for a given field that _may_ alias the given object
  // and do _not_ have the given value.
  void KillFieldInternal(HValue* object, int field, HValue* value) {
    if (field >= fields_.length()) return;  // Nothing to do.

    HFieldApproximation* approx = fields_[field];
    HFieldApproximation* prev = NULL;
    while (approx != NULL) {
      if (aliasing_->MayAlias(object, approx->object_)) {
        if (!Equal(approx->last_value_, value)) {
          // Kill an aliasing entry that doesn't agree on the value.
          if (prev != NULL) {
            prev->next_ = approx->next_;
          } else {
            fields_[field] = approx->next_;
          }
          approx = approx->next_;
          continue;
        }
      }
      prev = approx;
      approx = approx->next_;
    }
  }

  bool Equal(HValue* a, HValue* b) {
    if (a == b) return true;
400 401 402
    if (a != NULL && b != NULL && a->CheckFlag(HValue::kUseGVN)) {
      return a->Equals(b);
    }
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
    return false;
  }

  // Remove the last approximation for a field so that it can be reused.
  // We reuse the last entry because it was the first inserted and is thus
  // farthest away from the current instruction.
  HFieldApproximation* ReuseLastApproximation(int field) {
    HFieldApproximation* approx = fields_[field];
    ASSERT(approx != NULL);

    HFieldApproximation* prev = NULL;
    while (approx->next_ != NULL) {
      prev = approx;
      approx = approx->next_;
    }
    if (prev != NULL) prev->next_ = NULL;
    return approx;
  }

422 423 424
  // Compute the field index for the given object access; -1 if not tracked.
  int FieldOf(HObjectAccess access) {
    return access.IsInobject() ? FieldOf(access.offset()) : -1;
425 426
  }

427
  // Compute the field index for the given in-object offset; -1 if not tracked.
428 429
  int FieldOf(int offset) {
    if (offset >= kMaxTrackedFields * kPointerSize) return -1;
430 431
    // TODO(titzer): track misaligned loads in a separate list?
    if ((offset % kPointerSize) != 0) return -1;  // Ignore misaligned accesses.
432 433 434
    return offset / kPointerSize;
  }

435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
  // Ensure internal storage for the given number of fields.
  void EnsureFields(int num_fields) {
    if (fields_.length() < num_fields) {
      fields_.AddBlock(NULL, num_fields - fields_.length(), zone_);
    }
  }

  // Print this table to stdout.
  void Print() {
    for (int i = 0; i < fields_.length(); i++) {
      PrintF("  field %d: ", i);
      for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) {
        PrintF("[o%d =", a->object_->id());
        if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id());
        PrintF("] ");
      }
      PrintF("\n");
    }
  }

455 456 457 458 459 460
  Zone* zone_;
  ZoneList<HFieldApproximation*> fields_;
  HAliasAnalyzer* aliasing_;
};


461 462 463 464
// Support for HFlowEngine: collect store effects within loops.
class HLoadEliminationEffects : public ZoneObject {
 public:
  explicit HLoadEliminationEffects(Zone* zone)
465
    : zone_(zone), stores_(5, zone) { }
466 467 468

  inline bool Disabled() {
    return false;  // Effects are _not_ disabled.
469 470
  }

471 472
  // Process a possibly side-effecting instruction.
  void Process(HInstruction* instr, Zone* zone) {
473 474 475 476
    if (instr->IsStoreNamedField()) {
      stores_.Add(HStoreNamedField::cast(instr), zone_);
    } else {
      flags_.Add(instr->ChangesFlags());
477
    }
478
  }
479

480 481
  // Apply these effects to the given load elimination table.
  void Apply(HLoadEliminationTable* table) {
482 483 484
    // Loads must not be hoisted past the OSR entry, therefore we kill
    // everything if we see an OSR entry.
    if (flags_.Contains(kInobjectFields) || flags_.Contains(kOsrEntries)) {
485 486 487
      table->Kill();
      return;
    }
488
    if (flags_.Contains(kElementsKind) || flags_.Contains(kMaps)) {
489 490
      table->KillOffset(JSObject::kMapOffset);
    }
491
    if (flags_.Contains(kElementsKind) || flags_.Contains(kElementsPointer)) {
492 493 494 495 496
      table->KillOffset(JSObject::kElementsOffset);
    }

    // Kill non-agreeing fields for each store contained in these effects.
    for (int i = 0; i < stores_.length(); i++) {
497
      table->KillStore(stores_[i]);
498 499 500 501 502
    }
  }

  // Union these effects with the other effects.
  void Union(HLoadEliminationEffects* that, Zone* zone) {
503
    flags_.Add(that->flags_);
504 505
    for (int i = 0; i < that->stores_.length(); i++) {
      stores_.Add(that->stores_[i], zone);
506 507 508
    }
  }

509 510
 private:
  Zone* zone_;
511
  GVNFlagSet flags_;
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
  ZoneList<HStoreNamedField*> stores_;
};


// The main routine of the analysis phase. Use the HFlowEngine for either a
// local or a global analysis.
void HLoadEliminationPhase::Run() {
  HFlowEngine<HLoadEliminationTable, HLoadEliminationEffects>
    engine(graph(), zone());
  HAliasAnalyzer aliasing;
  HLoadEliminationTable* table =
      new(zone()) HLoadEliminationTable(zone(), &aliasing);

  if (GLOBAL) {
    // Perform a global analysis.
    engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table);
  } else {
    // Perform only local analysis.
    for (int i = 0; i < graph()->blocks()->length(); i++) {
      table->Kill();
      engine.AnalyzeOneBlock(graph()->blocks()->at(i), table);
    }
  }
}
536 537

} }  // namespace v8::internal