Improve GVN handling of ElementTransitions.

BUG=
TEST=

Review URL: https://chromiumcodereview.appspot.com/9141016

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@10651 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
danno@chromium.org 2012-02-09 08:58:19 +00:00
parent f833c4031b
commit d949c64688
7 changed files with 379 additions and 33 deletions

View File

@ -893,6 +893,13 @@ void HCheckInstanceType::GetCheckMaskAndTag(uint8_t* mask, uint8_t* tag) {
void HCheckMap::PrintDataTo(StringStream* stream) {
value()->PrintNameTo(stream);
stream->Add(" %p", *map());
if (mode() == REQUIRE_EXACT_MAP) {
stream->Add(" [EXACT]");
} else if (!has_element_transitions_) {
stream->Add(" [EXACT*]");
} else {
stream->Add(" [MATCH ELEMENTS]");
}
}

View File

@ -186,6 +186,7 @@ class LChunkBuilder;
V(InobjectFields) \
V(BackingStoreFields) \
V(ElementsKind) \
V(ElementsPointer) \
V(ArrayElements) \
V(DoubleArrayElements) \
V(SpecializedArrayElements) \
@ -646,6 +647,18 @@ class HValue: public ZoneObject {
return gvn_flags_.ContainsAnyOf(AllObservableSideEffectsFlagSet());
}
GVNFlagSet DependsOnFlags() const {
GVNFlagSet result = gvn_flags_;
result.Intersect(AllDependsOnFlagSet());
return result;
}
GVNFlagSet SideEffectFlags() const {
GVNFlagSet result = gvn_flags_;
result.Intersect(AllSideEffectsFlagSet());
return result;
}
GVNFlagSet ChangesFlags() const {
GVNFlagSet result = gvn_flags_;
result.Intersect(AllChangesFlagSet());
@ -722,6 +735,15 @@ class HValue: public ZoneObject {
representation_ = r;
}
static GVNFlagSet AllDependsOnFlagSet() {
GVNFlagSet result;
// Create changes mask.
#define ADD_FLAG(type) result.Add(kDependsOn##type);
GVN_FLAG_LIST(ADD_FLAG)
#undef ADD_FLAG
return result;
}
static GVNFlagSet AllChangesFlagSet() {
GVNFlagSet result;
// Create changes mask.
@ -743,6 +765,8 @@ class HValue: public ZoneObject {
static GVNFlagSet AllObservableSideEffectsFlagSet() {
GVNFlagSet result = AllChangesFlagSet();
result.Remove(kChangesElementsKind);
result.Remove(kChangesElementsPointer);
result.Remove(kChangesMaps);
return result;
}
@ -1920,8 +1944,7 @@ class HLoadElements: public HUnaryOperation {
explicit HLoadElements(HValue* value) : HUnaryOperation(value) {
set_representation(Representation::Tagged());
SetFlag(kUseGVN);
SetGVNFlag(kDependsOnMaps);
SetGVNFlag(kDependsOnElementsKind);
SetGVNFlag(kDependsOnElementsPointer);
}
virtual Representation RequiredInputRepresentation(int index) {
@ -1972,6 +1995,11 @@ class HCheckMap: public HTemplateInstruction<2> {
set_representation(Representation::Tagged());
SetFlag(kUseGVN);
SetGVNFlag(kDependsOnMaps);
// If the map to check doesn't have the untransitioned elements, it must not
// be hoisted above TransitionElements instructions.
if (mode == REQUIRE_EXACT_MAP || !map->has_fast_smi_only_elements()) {
SetGVNFlag(kDependsOnElementsKind);
}
has_element_transitions_ =
map->LookupElementsTransitionMap(FAST_DOUBLE_ELEMENTS, NULL) != NULL ||
map->LookupElementsTransitionMap(FAST_ELEMENTS, NULL) != NULL;
@ -4135,7 +4163,17 @@ class HTransitionElementsKind: public HTemplateInstruction<1> {
transitioned_map_(transitioned_map) {
SetOperandAt(0, object);
SetFlag(kUseGVN);
SetGVNFlag(kDependsOnMaps);
SetGVNFlag(kChangesElementsKind);
if (original_map->has_fast_double_elements()) {
SetGVNFlag(kChangesElementsPointer);
SetGVNFlag(kDependsOnElementsPointer);
SetGVNFlag(kDependsOnDoubleArrayElements);
} else if (transitioned_map->has_fast_double_elements()) {
SetGVNFlag(kChangesElementsPointer);
SetGVNFlag(kDependsOnElementsPointer);
SetGVNFlag(kDependsOnArrayElements);
}
set_representation(Representation::Tagged());
}

View File

@ -70,7 +70,8 @@ HBasicBlock::HBasicBlock(HGraph* graph)
deleted_phis_(4),
parent_loop_header_(NULL),
is_inline_return_target_(false),
is_deoptimizing_(false) { }
is_deoptimizing_(false),
dominates_loop_successors_(false) { }
void HBasicBlock::AttachLoopInformation() {
@ -315,6 +316,62 @@ void HBasicBlock::AssignCommonDominator(HBasicBlock* other) {
}
void HBasicBlock::AssignLoopSuccessorDominators() {
// Mark blocks that dominate all subsequent reachable blocks inside their
// loop. Exploit the fact that blocks are sorted in reverse post order. When
// the loop is visited in increasing block id order, if the number of
// non-loop-exiting successor edges at the dominator_candidate block doesn't
// exceed the number of previously encountered predecessor edges, there is no
// path from the loop header to any block with higher id that doesn't go
// through the dominator_candidate block. In this case, the
// dominator_candidate block is guaranteed to dominate all blocks reachable
// from it with higher ids.
HBasicBlock* last = loop_information()->GetLastBackEdge();
int outstanding_successors = 1; // one edge from the pre-header
// Header always dominates everything.
MarkAsLoopSuccessorDominator();
for (int j = block_id(); j <= last->block_id(); ++j) {
HBasicBlock* dominator_candidate = graph_->blocks()->at(j);
for (HPredecessorIterator it(dominator_candidate); !it.Done();
it.Advance()) {
HBasicBlock* predecessor = it.Current();
// Don't count back edges.
if (predecessor->block_id() < dominator_candidate->block_id()) {
outstanding_successors--;
}
}
// If more successors than predecessors have been seen in the loop up to
// now, it's not possible to guarantee that the current block dominates
// all of the blocks with higher IDs. In this case, assume conservatively
// that those paths through loop that don't go through the current block
// contain all of the loop's dependencies. Also be careful to record
// dominator information about the current loop that's being processed,
// and not nested loops, which will be processed when
// AssignLoopSuccessorDominators gets called on their header.
ASSERT(outstanding_successors >= 0);
HBasicBlock* parent_loop_header = dominator_candidate->parent_loop_header();
if (outstanding_successors == 0 &&
(parent_loop_header == this && !dominator_candidate->IsLoopHeader())) {
dominator_candidate->MarkAsLoopSuccessorDominator();
}
HControlInstruction* end = dominator_candidate->end();
for (HSuccessorIterator it(end); !it.Done(); it.Advance()) {
HBasicBlock* successor = it.Current();
// Only count successors that remain inside the loop and don't loop back
// to a loop header.
if (successor->block_id() > dominator_candidate->block_id() &&
successor->block_id() <= last->block_id()) {
// Backwards edges must land on loop headers.
ASSERT(successor->block_id() > dominator_candidate->block_id() ||
successor->IsLoopHeader());
outstanding_successors++;
}
}
}
}
int HBasicBlock::PredecessorIndexOf(HBasicBlock* predecessor) const {
for (int i = 0; i < predecessors_.length(); ++i) {
if (predecessors_[i] == predecessor) return i;
@ -750,10 +807,12 @@ void HGraph::Postorder(HBasicBlock* block,
void HGraph::AssignDominators() {
HPhase phase("Assign dominators", this);
for (int i = 0; i < blocks_.length(); ++i) {
if (blocks_[i]->IsLoopHeader()) {
HBasicBlock* block = blocks_[i];
if (block->IsLoopHeader()) {
// Only the first predecessor of a loop header is from outside the loop.
// All others are back edges, and thus cannot dominate the loop header.
blocks_[i]->AssignCommonDominator(blocks_[i]->predecessors()->first());
block->AssignCommonDominator(block->predecessors()->first());
block->AssignLoopSuccessorDominators();
} else {
for (int j = blocks_[i]->predecessors()->length() - 1; j >= 0; --j) {
blocks_[i]->AssignCommonDominator(blocks_[i]->predecessors()->at(j));
@ -1371,7 +1430,8 @@ class HGlobalValueNumberer BASE_EMBEDDED {
void LoopInvariantCodeMotion();
void ProcessLoopBlock(HBasicBlock* block,
HBasicBlock* before_loop,
GVNFlagSet loop_kills);
GVNFlagSet loop_kills,
GVNFlagSet* accumulated_first_time_depends);
bool AllowCodeMotion();
bool ShouldMove(HInstruction* instr, HBasicBlock* loop_header);
@ -1396,6 +1456,7 @@ class HGlobalValueNumberer BASE_EMBEDDED {
bool HGlobalValueNumberer::Analyze() {
removed_side_effects_ = false;
ComputeBlockSideEffects();
if (FLAG_loop_invariant_code_motion) {
LoopInvariantCodeMotion();
@ -1407,6 +1468,12 @@ bool HGlobalValueNumberer::Analyze() {
void HGlobalValueNumberer::ComputeBlockSideEffects() {
// The Analyze phase of GVN can be called multiple times. Clear loop side
// effects before computing them to erase the contents from previous Analyze
// passes.
for (int i = 0; i < loop_side_effects_.length(); ++i) {
loop_side_effects_[i].RemoveAll();
}
for (int i = graph_->blocks()->length() - 1; i >= 0; --i) {
// Compute side effects for the block.
HBasicBlock* block = graph_->blocks()->at(i);
@ -1444,18 +1511,22 @@ void HGlobalValueNumberer::LoopInvariantCodeMotion() {
block->block_id(),
side_effects.ToIntegral());
GVNFlagSet accumulated_first_time_depends;
HBasicBlock* last = block->loop_information()->GetLastBackEdge();
for (int j = block->block_id(); j <= last->block_id(); ++j) {
ProcessLoopBlock(graph_->blocks()->at(j), block, side_effects);
ProcessLoopBlock(graph_->blocks()->at(j), block, side_effects,
&accumulated_first_time_depends);
}
}
}
}
void HGlobalValueNumberer::ProcessLoopBlock(HBasicBlock* block,
HBasicBlock* loop_header,
GVNFlagSet loop_kills) {
void HGlobalValueNumberer::ProcessLoopBlock(
HBasicBlock* block,
HBasicBlock* loop_header,
GVNFlagSet loop_kills,
GVNFlagSet* accumulated_first_time_depends) {
HBasicBlock* pre_header = loop_header->predecessors()->at(0);
GVNFlagSet depends_flags = HValue::ConvertChangesToDependsFlags(loop_kills);
TraceGVN("Loop invariant motion for B%d depends_flags=0x%x\n",
@ -1464,25 +1535,65 @@ void HGlobalValueNumberer::ProcessLoopBlock(HBasicBlock* block,
HInstruction* instr = block->first();
while (instr != NULL) {
HInstruction* next = instr->next();
if (instr->CheckFlag(HValue::kUseGVN) &&
!instr->gvn_flags().ContainsAnyOf(depends_flags)) {
TraceGVN("Checking instruction %d (%s)\n",
bool hoisted = false;
if (instr->CheckFlag(HValue::kUseGVN)) {
TraceGVN("Checking instruction %d (%s) instruction GVN flags 0x%X, "
"loop kills 0x%X\n",
instr->id(),
instr->Mnemonic());
bool inputs_loop_invariant = true;
for (int i = 0; i < instr->OperandCount(); ++i) {
if (instr->OperandAt(i)->IsDefinedAfter(pre_header)) {
inputs_loop_invariant = false;
}
instr->Mnemonic(),
instr->gvn_flags().ToIntegral(),
depends_flags.ToIntegral());
bool can_hoist = !instr->gvn_flags().ContainsAnyOf(depends_flags);
if (!can_hoist && instr->IsTransitionElementsKind()) {
// It's only possible to hoist one time side effects if there are no
// dependencies on their changes from the loop header to the current
// instruction.
GVNFlagSet converted_changes =
HValue::ConvertChangesToDependsFlags(instr->ChangesFlags());
TraceGVN("Checking dependencies on one-time instruction %d (%s) "
"converted changes 0x%X, accumulated depends 0x%X\n",
instr->id(),
instr->Mnemonic(),
converted_changes.ToIntegral(),
accumulated_first_time_depends->ToIntegral());
// It's possible to hoist one-time side effects from the current loop
// loop only if they dominate all of the successor blocks in the same
// loop and there are not any instructions that have Changes/DependsOn
// that intervene between it and the beginning of the loop header.
bool in_nested_loop = block != loop_header &&
((block->parent_loop_header() != loop_header) ||
block->IsLoopHeader());
can_hoist = !in_nested_loop &&
block->IsLoopSuccessorDominator() &&
!accumulated_first_time_depends->ContainsAnyOf(converted_changes);
}
if (inputs_loop_invariant && ShouldMove(instr, loop_header)) {
TraceGVN("Found loop invariant instruction %d\n", instr->id());
// Move the instruction out of the loop.
instr->Unlink();
instr->InsertBefore(pre_header->end());
if (can_hoist) {
bool inputs_loop_invariant = true;
for (int i = 0; i < instr->OperandCount(); ++i) {
if (instr->OperandAt(i)->IsDefinedAfter(pre_header)) {
inputs_loop_invariant = false;
}
}
if (inputs_loop_invariant && ShouldMove(instr, loop_header)) {
TraceGVN("Hoisting loop invariant instruction %d\n", instr->id());
// Move the instruction out of the loop.
instr->Unlink();
instr->InsertBefore(pre_header->end());
if (instr->HasSideEffects()) removed_side_effects_ = true;
hoisted = true;
}
}
}
if (!hoisted) {
// If an instruction is not hoisted, we have to account for its side
// effects when hoisting later HTransitionElementsKind instructions.
accumulated_first_time_depends->Add(instr->DependsOnFlags());
GVNFlagSet converted_changes =
HValue::ConvertChangesToDependsFlags(instr->SideEffectFlags());
accumulated_first_time_depends->Add(converted_changes);
}
instr = next;
}
}
@ -2390,7 +2501,8 @@ HGraph* HGraphBuilder::CreateGraph() {
// could only be discovered by removing side-effect-generating instructions
// during the first pass.
if (FLAG_smi_only_arrays && removed_side_effects) {
gvn.Analyze();
removed_side_effects = gvn.Analyze();
ASSERT(!removed_side_effects);
}
}
@ -7260,7 +7372,10 @@ void HTracer::Trace(const char* name, HGraph* graph, LChunk* chunk) {
}
PrintEmptyProperty("xhandlers");
PrintEmptyProperty("flags");
const char* flags = current->IsLoopSuccessorDominator()
? "dom-loop-succ"
: "";
PrintStringProperty("flags", flags);
if (current->dominator() != NULL) {
PrintBlockProperty("dominator", current->dominator()->block_id());

View File

@ -126,6 +126,7 @@ class HBasicBlock: public ZoneObject {
int PredecessorIndexOf(HBasicBlock* predecessor) const;
void AddSimulate(int ast_id) { AddInstruction(CreateSimulate(ast_id)); }
void AssignCommonDominator(HBasicBlock* other);
void AssignLoopSuccessorDominators();
void FinishExitWithDeoptimization(HDeoptimize::UseEnvironment has_uses) {
FinishExit(CreateDeoptimize(has_uses));
@ -149,6 +150,13 @@ class HBasicBlock: public ZoneObject {
bool IsDeoptimizing() const { return is_deoptimizing_; }
void MarkAsDeoptimizing() { is_deoptimizing_ = true; }
bool IsLoopSuccessorDominator() const {
return dominates_loop_successors_;
}
void MarkAsLoopSuccessorDominator() {
dominates_loop_successors_ = true;
}
inline Zone* zone();
#ifdef DEBUG
@ -182,6 +190,22 @@ class HBasicBlock: public ZoneObject {
HBasicBlock* parent_loop_header_;
bool is_inline_return_target_;
bool is_deoptimizing_;
bool dominates_loop_successors_;
};
class HPredecessorIterator BASE_EMBEDDED {
public:
explicit HPredecessorIterator(HBasicBlock* block)
: predecessor_list_(block->predecessors()), current_(0) { }
bool Done() { return current_ >= predecessor_list_->length(); }
HBasicBlock* Current() { return predecessor_list_->at(current_); }
void Advance() { current_++; }
private:
const ZoneList<HBasicBlock*>* predecessor_list_;
int current_;
};

View File

@ -5511,7 +5511,7 @@ class PolymorphicCodeCacheHashTableKey : public HashTableKey {
for (int i = 0; i < maps_->length(); ++i) {
bool match_found = false;
for (int j = 0; j < other_maps.length(); ++j) {
if (maps_->at(i)->EquivalentTo(*other_maps.at(j))) {
if (*(maps_->at(i)) == *(other_maps.at(j))) {
match_found = true;
break;
}

View File

@ -4696,12 +4696,6 @@ class Map: public HeapObject {
// The "shared" flags of both this map and |other| are ignored.
bool EquivalentToForNormalization(Map* other, PropertyNormalizationMode mode);
// Returns true if this map and |other| describe equivalent objects.
// The "shared" flags of both this map and |other| are ignored.
bool EquivalentTo(Map* other) {
return EquivalentToForNormalization(other, KEEP_INOBJECT_PROPERTIES);
}
// Returns the contents of this map's descriptor array for the given string.
// May return NULL. |safe_to_add_transition| is set to false and NULL
// is returned if adding transitions is not allowed.

View File

@ -0,0 +1,168 @@
// Copyright 2011 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 --smi-only-arrays
// Ensure that ElementsKind transitions in various situations are hoisted (or
// not hoisted) correctly, don't change the semantics programs and don't trigger
// deopt through hoisting in important situations.
support_smi_only_arrays = %HasFastSmiOnlyElements(new Array(1,2,3,4,5,6));
if (support_smi_only_arrays) {
print("Tests include smi-only arrays.");
} else {
print("Tests do NOT include smi-only arrays.");
}
if (support_smi_only_arrays) {
// Make sure that a simple elements array transitions inside a loop before
// stores to an array gets hoisted in a way that doesn't generate a deopt in
// simple cases.}
function testDoubleConversion4(a) {
var object = new Object();
a[0] = 0;
var count = 3;
do {
a[0] = object;
} while (--count > 0);
}
testDoubleConversion4(new Array(5));
%OptimizeFunctionOnNextCall(testDoubleConversion4);
testDoubleConversion4(new Array(5));
testDoubleConversion4(new Array(5));
assertTrue(2 != %GetOptimizationStatus(testDoubleConversion4));
// Make sure that non-element related map checks that are not preceded by
// transitions in a loop still get hoisted in a way that doesn't generate a
// deopt in simple cases.
function testExactMapHoisting(a) {
var object = new Object();
a.foo = 0;
a[0] = 0;
a[1] = 1;
var count = 3;
do {
a.foo = object; // This map check should be hoistable
a[1] = object;
result = a.foo == object && a[1] == object;
} while (--count > 0);
}
testExactMapHoisting(new Array(5));
%OptimizeFunctionOnNextCall(testExactMapHoisting);
testExactMapHoisting(new Array(5));
testExactMapHoisting(new Array(5));
assertTrue(2 != %GetOptimizationStatus(testExactMapHoisting));
// Make sure that non-element related map checks do NOT get hoisted if they
// depend on an elements transition before them and it's not possible to hoist
// that transition.
function testExactMapHoisting2(a) {
var object = new Object();
a.foo = 0;
a[0] = 0;
a[1] = 1;
var count = 3;
do {
if (a.bar === undefined) {
a[1] = 2.5;
}
a.foo = object; // This map check should NOT be hoistable because it
// includes a check for the FAST_ELEMENTS map as well as
// the FAST_DOUBLE_ELEMENTS map, which depends on the
// double transition above in the if, which cannot be
// hoisted.
} while (--count > 0);
}
testExactMapHoisting2(new Array(5));
%OptimizeFunctionOnNextCall(testExactMapHoisting2);
testExactMapHoisting2(new Array(5));
testExactMapHoisting2(new Array(5));
assertTrue(2 != %GetOptimizationStatus(testExactMapHoisting2));
// Make sure that non-element related map checks do get hoisted if they use
// the transitioned map for the check and all transitions that they depend
// upon can hoisted, too.
function testExactMapHoisting3(a) {
var object = new Object();
a.foo = 0;
a[0] = 0;
a[1] = 1;
var count = 3;
do {
a[1] = 2.5;
a.foo = object; // This map check should be hoistable because all elements
// transitions in the loop can also be hoisted.
} while (--count > 0);
}
var add_transition = new Array(5);
add_transition.foo = 0;
add_transition[0] = new Object(); // For FAST_ELEMENT transition to be created
testExactMapHoisting3(new Array(5));
%OptimizeFunctionOnNextCall(testExactMapHoisting3);
testExactMapHoisting3(new Array(5));
testExactMapHoisting3(new Array(5));
assertTrue(2 != %GetOptimizationStatus(testExactMapHoisting3));
function testDominatingTransitionHoisting1(a) {
var object = new Object();
a[0] = 0;
var count = 3;
do {
if (a.baz != true) {
a[1] = 2.5;
}
a[0] = object;
} while (--count > 3);
}
testDominatingTransitionHoisting1(new Array(5));
%OptimizeFunctionOnNextCall(testDominatingTransitionHoisting1);
testDominatingTransitionHoisting1(new Array(5));
testDominatingTransitionHoisting1(new Array(5));
assertTrue(2 != %GetOptimizationStatus(testDominatingTransitionHoisting1));
function testHoistingWithSideEffect(a) {
var object = new Object();
a[0] = 0;
var count = 3;
do {
assertTrue(true);
a[0] = object;
} while (--count > 3);
}
testHoistingWithSideEffect(new Array(5));
%OptimizeFunctionOnNextCall(testHoistingWithSideEffect);
testHoistingWithSideEffect(new Array(5));
testHoistingWithSideEffect(new Array(5));
assertTrue(2 != %GetOptimizationStatus(testHoistingWithSideEffect));
}