Implement global load elimination based on flow engine.

BUG=
R=mvstanton@chromium.org

Review URL: https://codereview.chromium.org/27148004

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17273 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
titzer@chromium.org 2013-10-18 14:28:24 +00:00
parent a428465819
commit 4d0f2cdd3b
3 changed files with 513 additions and 98 deletions

View File

@ -28,10 +28,14 @@
#include "hydrogen-alias-analysis.h"
#include "hydrogen-load-elimination.h"
#include "hydrogen-instructions.h"
#include "hydrogen-flow-engine.h"
namespace v8 {
namespace internal {
#define GLOBAL true
#define TRACE(x) if (FLAG_trace_load_elimination) PrintF x
static const int kMaxTrackedFields = 16;
static const int kMaxTrackedObjects = 5;
@ -42,17 +46,145 @@ class HFieldApproximation : public ZoneObject {
HLoadNamedField* last_load_;
HValue* last_value_;
HFieldApproximation* next_;
// 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_load_ = this->last_load_;
copy->last_value_ = this->last_value_;
copy->next_ = this->next_->Copy(zone);
return copy;
}
};
// 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.
class HLoadEliminationTable BASE_EMBEDDED {
class HLoadEliminationTable : public ZoneObject {
public:
HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing)
: zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { }
// 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);
if (result != instr) {
// 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;
}
default: {
if (instr->CheckGVNFlag(kChangesInobjectFields)) {
TRACE((" kill-all i%d\n", instr->id()));
Kill();
break;
}
if (instr->CheckGVNFlag(kChangesMaps)) {
TRACE((" kill-maps i%d\n", instr->id()));
KillOffset(JSObject::kMapOffset);
}
if (instr->CheckGVNFlag(kChangesElementsKind)) {
TRACE((" kill-elements-kind i%d\n", instr->id()));
KillOffset(JSObject::kMapOffset);
KillOffset(JSObject::kElementsOffset);
}
if (instr->CheckGVNFlag(kChangesElementsPointer)) {
TRACE((" kill-elements i%d\n", instr->id()));
KillOffset(JSObject::kElementsOffset);
}
if (instr->CheckGVNFlag(kChangesOsrEntries)) {
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;
}
// Support for global analysis with HFlowEngine: Copy state to sucessor block.
HLoadEliminationTable* Copy(HBasicBlock* succ, Zone* zone) {
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;
}
// Support for global analysis with HFlowEngine: Merge this state with
// the other incoming state.
HLoadEliminationTable* Merge(HBasicBlock* succ,
HLoadEliminationTable* that, Zone* zone) {
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_;
}
}
return this;
}
friend class HLoadEliminationEffects; // Calls Kill() and others.
friend class HLoadEliminationPhase;
private:
// 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}.
@ -118,28 +250,17 @@ class HLoadEliminationTable BASE_EMBEDDED {
}
}
// Compute the field index for the given object access; -1 if not tracked.
int FieldOf(HObjectAccess access) {
// Only track kMaxTrackedFields in-object fields.
if (!access.IsInobject()) return -1;
return FieldOf(access.offset());
}
// 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_load_ != NULL) PrintF(" L%d", a->last_load_->id());
if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id());
PrintF("] ");
}
PrintF("\n");
// 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_;
}
return NULL;
}
private:
// Find or create an entry for the given object and field pair.
HFieldApproximation* FindOrCreate(HValue* object, int field) {
EnsureFields(field + 1);
@ -218,110 +339,143 @@ class HLoadEliminationTable BASE_EMBEDDED {
return approx;
}
// Ensure internal storage for the given number of fields.
void EnsureFields(int num_fields) {
while (fields_.length() < num_fields) fields_.Add(NULL, zone_);
// Compute the field index for the given object access; -1 if not tracked.
int FieldOf(HObjectAccess access) {
return access.IsInobject() ? FieldOf(access.offset()) : -1;
}
// Compute the field index for the given in-object offset.
// Compute the field index for the given in-object offset; -1 if not tracked.
int FieldOf(int offset) {
if (offset >= kMaxTrackedFields * kPointerSize) return -1;
ASSERT((offset % kPointerSize) == 0); // Assume aligned accesses.
return offset / kPointerSize;
}
// 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_load_ != NULL) PrintF(" L%d", a->last_load_->id());
if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id());
PrintF("] ");
}
PrintF("\n");
}
}
Zone* zone_;
ZoneList<HFieldApproximation*> fields_;
HAliasAnalyzer* aliasing_;
};
void HLoadEliminationPhase::Run() {
for (int i = 0; i < graph()->blocks()->length(); i++) {
HBasicBlock* block = graph()->blocks()->at(i);
EliminateLoads(block);
// Support for HFlowEngine: collect store effects within loops.
class HLoadEliminationEffects : public ZoneObject {
public:
explicit HLoadEliminationEffects(Zone* zone)
: zone_(zone),
maps_stored_(false),
fields_stored_(false),
elements_stored_(false),
stores_(5, zone) { }
inline bool Disabled() {
return false; // Effects are _not_ disabled.
}
}
// For code de-uglification.
#define TRACE(x) if (FLAG_trace_load_elimination) PrintF x
// Eliminate loads and stores local to a block.
void HLoadEliminationPhase::EliminateLoads(HBasicBlock* block) {
HAliasAnalyzer aliasing;
HLoadEliminationTable table(zone(), &aliasing);
TRACE(("-- load-elim B%d -------------------------------------------------\n",
block->block_id()));
for (HInstructionIterator it(block); !it.Done(); it.Advance()) {
bool changed = false;
HInstruction* instr = it.Current();
// Process a possibly side-effecting instruction.
void Process(HInstruction* instr, Zone* zone) {
switch (instr->opcode()) {
case HValue::kLoadNamedField: {
HLoadNamedField* load = HLoadNamedField::cast(instr);
TRACE((" process L%d field %d (o%d)\n",
instr->id(),
table.FieldOf(load->access()),
load->object()->ActualValue()->id()));
HValue* result = table.load(load);
if (result != instr) {
// 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);
}
changed = true;
case HValue::kStoreNamedField: {
stores_.Add(HStoreNamedField::cast(instr), zone_);
break;
}
case HValue::kStoreNamedField: {
HStoreNamedField* store = HStoreNamedField::cast(instr);
TRACE((" process S%d field %d (o%d) = v%d\n",
instr->id(),
table.FieldOf(store->access()),
store->object()->ActualValue()->id(),
store->value()->id()));
HValue* result = table.store(store);
if (result == NULL) {
// The store is redundant. Remove it.
TRACE((" remove S%d\n", instr->id()));
instr->DeleteAndReplaceWith(NULL);
}
changed = true;
break;
case HValue::kOsrEntry: {
// Kill everything. Loads must not be hoisted past the OSR entry.
maps_stored_ = true;
fields_stored_ = true;
elements_stored_ = true;
}
default: {
if (instr->CheckGVNFlag(kChangesInobjectFields)) {
TRACE((" kill-all i%d\n", instr->id()));
table.Kill();
continue;
}
if (instr->CheckGVNFlag(kChangesMaps)) {
TRACE((" kill-maps i%d\n", instr->id()));
table.KillOffset(JSObject::kMapOffset);
}
if (instr->CheckGVNFlag(kChangesElementsKind)) {
TRACE((" kill-elements-kind i%d\n", instr->id()));
table.KillOffset(JSObject::kMapOffset);
table.KillOffset(JSObject::kElementsOffset);
}
if (instr->CheckGVNFlag(kChangesElementsPointer)) {
TRACE((" kill-elements i%d\n", instr->id()));
table.KillOffset(JSObject::kElementsOffset);
}
fields_stored_ |= instr->CheckGVNFlag(kChangesInobjectFields);
maps_stored_ |= instr->CheckGVNFlag(kChangesMaps);
maps_stored_ |= instr->CheckGVNFlag(kChangesElementsKind);
elements_stored_ |= instr->CheckGVNFlag(kChangesElementsKind);
elements_stored_ |= instr->CheckGVNFlag(kChangesElementsPointer);
}
// Improvements possible:
// - learn from HCheckMaps for field 0
// - remove unobservable stores (write-after-write)
}
}
// Apply these effects to the given load elimination table.
void Apply(HLoadEliminationTable* table) {
if (fields_stored_) {
table->Kill();
return;
}
if (maps_stored_) {
table->KillOffset(JSObject::kMapOffset);
}
if (elements_stored_) {
table->KillOffset(JSObject::kElementsOffset);
}
if (changed && FLAG_trace_load_elimination) {
table.Print();
// Kill non-agreeing fields for each store contained in these effects.
for (int i = 0; i < stores_.length(); i++) {
HStoreNamedField* s = stores_[i];
int field = table->FieldOf(s->access());
if (field >= 0) {
table->KillFieldInternal(s->object()->ActualValue(), field, s->value());
}
}
}
// Union these effects with the other effects.
void Union(HLoadEliminationEffects* that, Zone* zone) {
maps_stored_ |= that->maps_stored_;
fields_stored_ |= that->fields_stored_;
elements_stored_ |= that->elements_stored_;
for (int i = 0; i < that->stores_.length(); i++) {
stores_.Add(that->stores_[i], zone);
}
}
private:
Zone* zone_;
bool maps_stored_ : 1;
bool fields_stored_ : 1;
bool elements_stored_ : 1;
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);
}
}
}
} } // namespace v8::internal

View File

@ -0,0 +1,196 @@
// 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.
// Flags: --allow-natives-syntax --load-elimination
// Test global load elimination of redundant loads and stores.
var X = true; // For forcing branches.
X = false;
X = true;
X = false;
function B(x, y) {
this.x = x;
this.y = y;
return this;
}
function test_load() {
var a = new B(1, 2);
var f = a.x + a.x;
if (false) ;
return f + a.x + a.x;
}
function test_load2() {
var a = new B(1, 2);
var f = a.x + a.x;
if (true) ;
return f + a.x + a.x;
}
function test_store_load() {
var a = new B(1, 2);
a.x = 4;
var b = X ? a.x : a.x;
return b + a.x;
}
function test_store_load2() {
var a = new B(1, 2);
var c = 6;
if (X) a.x = c;
else a.x = c;
return a.x + a.x;
}
function test_nonaliasing_store1() {
var a = new B(2, 3), b = new B(3, 4);
if (X) ;
b.x = 4;
if (X) ;
var f = a.x;
if (X) ;
b.x = 5;
if (X) ;
var g = a.x;
if (X) ;
b.x = 6;
if (X) ;
var h = a.x;
if (X) ;
b.x = 7;
if (X) ;
return f + g + h + a.x;
}
function test_loop(x) {
var a = new B(2, 3);
var v = a.x;
var total = v;
var i = 0;
while (i++ < 10) {
total = a.x;
a.y = 4;
}
return total;
}
function test_loop2(x) {
var a = new B(2, 3);
var v = a.x;
var total = v;
var i = 0;
while (i++ < 10) {
total = a.x; // a.x not affected by loop
a.y = 4;
var j = 0;
while (j++ < 10) {
total = a.x; // a.x not affected by loop
a.y = 5;
}
total = a.x;
a.y = 6;
j = 0;
while (j++ < 10) {
total = a.x; // a.x not affected by loop
a.y = 7;
}
}
return total;
}
function killall() {
try { } catch(e) { }
}
%NeverOptimizeFunction(killall);
function test_store_load_kill() {
var a = new B(1, 2);
if (X) ;
a.x = 4;
if (X) ;
var f = a.x;
if (X) ;
a.x = 5;
if (X) ;
var g = a.x;
if (X) ;
killall();
if (X) ;
a.x = 6;
if (X) ;
var h = a.x;
if (X) ;
a.x = 7;
if (X) ;
return f + g + h + a.x;
}
function test_store_store() {
var a = new B(6, 7);
if (X) ;
a.x = 7;
if (X) ;
a.x = 7;
if (X) ;
a.x = 7;
if (X) ;
a.x = 7;
if (X) ;
return a.x;
}
function test(x, f) {
X = true;
assertEquals(x, f());
assertEquals(x, f());
X = false;
assertEquals(x, f());
assertEquals(x, f());
X = true;
%OptimizeFunctionOnNextCall(f);
assertEquals(x, f());
assertEquals(x, f());
X = false;
assertEquals(x, f());
assertEquals(x, f());
}
test(4, test_load);
test(8, test_store_load);
test(12, test_store_load2);
test(8, test_nonaliasing_store1);
test(22, test_store_load_kill);
test(7, test_store_store);
test(2, test_loop);
test(2, test_loop2);

View File

@ -0,0 +1,65 @@
// 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.
// Flags: --allow-natives-syntax --load-elimination
// Test global load elimination in the presence of OSR compilation.
function It(x, y) { }
function foo_osr(x, limit) {
var o = new It();
o.x = x;
o.y = x;
for (var i = 0; i < limit; i++) {
o.y += o.x; // Load of x cannot be hoisted due to OSR.
}
return o.y;
}
assertEquals(22, foo_osr(11, 1));
assertEquals(24, foo_osr(12, 1));
assertEquals(1300013, foo_osr(13, 100000));
function foo_hot(x, limit) {
var o = new It();
o.x = x;
o.y = x;
for (var i = 0; i < limit; i++) {
o.y += o.x; // Load of x can be hoisted without OSR.
}
return o.y;
}
assertEquals(22, foo_hot(11, 1));
assertEquals(24, foo_hot(12, 1));
%OptimizeFunctionOnNextCall(foo_hot);
assertEquals(32, foo_hot(16, 1));
assertEquals(1300013, foo_hot(13, 100000));