Commit 93113da5 authored by mmassi@chromium.org's avatar mmassi@chromium.org

Eliminate redundant array bound checks (checks already performed earlier in the DT).

As a special case, for checks on index expressions with the form (expr + constant) if a smaller constant is checked later in the DT also eliminate the check.
Finally, if a larger constant is checked later in the same BB do the more general check (larger constant) earlier instead of the less general one.
This will not cause useless deoptimizations because, since we are in the same BB, all the checks would have been executed anyway.
BUG=
TEST=

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@11437 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 21fc0fef
......@@ -195,6 +195,8 @@ DEFINE_bool(trap_on_deopt, false, "put a break point before deoptimizing")
DEFINE_bool(deoptimize_uncommon_cases, true, "deoptimize uncommon cases")
DEFINE_bool(polymorphic_inlining, true, "polymorphic inlining")
DEFINE_bool(use_osr, true, "use on-stack replacement")
DEFINE_bool(array_bounds_checks_elimination, true,
"perform array bounds checks elimination")
DEFINE_bool(trace_osr, false, "trace on-stack replacement")
DEFINE_int(stress_runs, 0, "number of stress runs")
......
......@@ -416,6 +416,7 @@ void HValue::Kill() {
SetFlag(kIsDead);
for (int i = 0; i < OperandCount(); ++i) {
HValue* operand = OperandAt(i);
if (operand == NULL) continue;
HUseListNode* first = operand->use_list_;
if (first != NULL && first->value() == this && first->index() == i) {
operand->use_list_ = first->tail();
......@@ -612,6 +613,7 @@ void HInstruction::Verify() {
HBasicBlock* cur_block = block();
for (int i = 0; i < OperandCount(); ++i) {
HValue* other_operand = OperandAt(i);
if (other_operand == NULL) continue;
HBasicBlock* other_block = other_operand->block();
if (cur_block == other_block) {
if (!other_operand->IsPhi()) {
......
......@@ -2727,29 +2727,328 @@ HGraph* HGraphBuilder::CreateGraph() {
HStackCheckEliminator sce(graph());
sce.Process();
// Replace the results of check instructions with the original value, if the
// result is used. This is safe now, since we don't do code motion after this
// point. It enables better register allocation since the value produced by
// check instructions is really a copy of the original value.
graph()->ReplaceCheckedValues();
graph()->EliminateRedundantBoundsChecks();
return graph();
}
void HGraph::ReplaceCheckedValues() {
HPhase phase("H_Replace checked values", this);
for (int i = 0; i < blocks()->length(); ++i) {
HInstruction* instr = blocks()->at(i)->first();
while (instr != NULL) {
if (instr->IsBoundsCheck()) {
// Replace all uses of the checked value with the original input.
ASSERT(instr->UseCount() > 0);
instr->ReplaceAllUsesWith(HBoundsCheck::cast(instr)->index());
// We try to "factor up" HBoundsCheck instructions towards the root of the
// dominator tree.
// For now we handle checks where the index is like "exp + int32value".
// If in the dominator tree we check "exp + v1" and later (dominated)
// "exp + v2", if v2 <= v1 we can safely remove the second check, and if
// v2 > v1 we can use v2 in the 1st check and again remove the second.
// To do so we keep a dictionary of all checks where the key if the pair
// "exp, length".
// The class BoundsCheckKey represents this key.
class BoundsCheckKey : public ZoneObject {
public:
HValue* IndexBase() const { return index_base_; }
HValue* Length() const { return length_; }
uint32_t Hash() {
return static_cast<uint32_t>(index_base_->Hashcode() ^ length_->Hashcode());
}
static BoundsCheckKey* Create(Zone* zone,
HBoundsCheck* check,
int32_t* offset) {
HValue* index_base = NULL;
HConstant* constant = NULL;
bool is_sub = false;
if (check->index()->IsAdd()) {
HAdd* index = HAdd::cast(check->index());
if (index->left()->IsConstant()) {
constant = HConstant::cast(index->left());
index_base = index->right();
} else if (index->right()->IsConstant()) {
constant = HConstant::cast(index->right());
index_base = index->left();
}
instr = instr->next();
} else if (check->index()->IsSub()) {
HSub* index = HSub::cast(check->index());
is_sub = true;
if (index->left()->IsConstant()) {
constant = HConstant::cast(index->left());
index_base = index->right();
} else if (index->right()->IsConstant()) {
constant = HConstant::cast(index->right());
index_base = index->left();
}
}
if (constant != NULL && constant->HasInteger32Value()) {
*offset = is_sub ? - constant->Integer32Value()
: constant->Integer32Value();
} else {
*offset = 0;
index_base = check->index();
}
return new(zone) BoundsCheckKey(index_base, check->length());
}
private:
BoundsCheckKey(HValue* index_base, HValue* length)
: index_base_(index_base),
length_(length) { }
HValue* index_base_;
HValue* length_;
};
// Data about each HBoundsCheck that can be eliminated or moved.
// It is the "value" in the dictionary indexed by "base-index, length"
// (the key is BoundsCheckKey).
// We scan the code with a dominator tree traversal.
// Traversing the dominator tree we keep a stack (implemented as a singly
// linked list) of "data" for each basic block that contains a relevant check
// with the same key (the dictionary holds the head of the list).
// We also keep all the "data" created for a given basic block in a list, and
// use it to "clean up" the dictionary when backtracking in the dominator tree
// traversal.
// Doing this each dictionary entry always directly points to the check that
// is dominating the code being examined now.
// We also track the current "offset" of the index expression and use it to
// decide if any check is already "covered" (so it can be removed) or not.
class BoundsCheckBbData: public ZoneObject {
public:
BoundsCheckKey* Key() const { return key_; }
int32_t LowerOffset() const { return lower_offset_; }
int32_t UpperOffset() const { return upper_offset_; }
HBasicBlock* BasicBlock() const { return basic_block_; }
HBoundsCheck* Check() const { return check_; }
BoundsCheckBbData* NextInBasicBlock() const { return next_in_bb_; }
BoundsCheckBbData* FatherInDominatorTree() const { return father_in_dt_; }
bool OffsetIsCovered(int32_t offset) const {
return offset >= LowerOffset() && offset <= UpperOffset();
}
// This method removes new_check and modifies the current check so that it
// also "covers" what new_check covered.
// The obvious precondition is that new_check follows Check() in the
// same basic block, and that new_offset is not covered (otherwise we
// could simply remove new_check).
// As a consequence LowerOffset() or UpperOffset() change (the covered
// range grows).
//
// In the general case the check covering the current range should be like
// these two checks:
// 0 <= Key()->IndexBase() + LowerOffset()
// Key()->IndexBase() + UpperOffset() < Key()->Length()
//
// We can transform the second check like this:
// Key()->IndexBase() + LowerOffset() <
// Key()->Length() + (LowerOffset() - UpperOffset())
// so we can handle both checks with a single unsigned comparison.
//
// The bulk of this method changes Check()->index() and Check()->length()
// replacing them with new HAdd instructions to perform the transformation
// described above.
void CoverCheck(HBoundsCheck* new_check,
int32_t new_offset) {
ASSERT(new_check->index()->representation().IsInteger32());
if (new_offset > upper_offset_) {
upper_offset_ = new_offset;
} else if (new_offset < lower_offset_) {
lower_offset_ = new_offset;
} else {
ASSERT(false);
}
BuildOffsetAdd(&added_index_,
&added_index_offset_,
Key()->IndexBase(),
new_check->index()->representation(),
lower_offset_);
Check()->SetOperandAt(0, added_index_);
BuildOffsetAdd(&added_length_,
&added_length_offset_,
Key()->Length(),
new_check->length()->representation(),
lower_offset_ - upper_offset_);
Check()->SetOperandAt(1, added_length_);
new_check->DeleteAndReplaceWith(NULL);
}
void RemoveZeroOperations() {
RemoveZeroAdd(&added_index_, &added_index_offset_);
RemoveZeroAdd(&added_length_, &added_length_offset_);
}
BoundsCheckBbData(BoundsCheckKey* key,
int32_t lower_offset,
int32_t upper_offset,
HBasicBlock* bb,
HBoundsCheck* check,
BoundsCheckBbData* next_in_bb,
BoundsCheckBbData* father_in_dt)
: key_(key),
lower_offset_(lower_offset),
upper_offset_(upper_offset),
basic_block_(bb),
check_(check),
added_index_offset_(NULL),
added_index_(NULL),
added_length_offset_(NULL),
added_length_(NULL),
next_in_bb_(next_in_bb),
father_in_dt_(father_in_dt) { }
private:
BoundsCheckKey* key_;
int32_t lower_offset_;
int32_t upper_offset_;
HBasicBlock* basic_block_;
HBoundsCheck* check_;
HConstant* added_index_offset_;
HAdd* added_index_;
HConstant* added_length_offset_;
HAdd* added_length_;
BoundsCheckBbData* next_in_bb_;
BoundsCheckBbData* father_in_dt_;
void BuildOffsetAdd(HAdd** add,
HConstant** constant,
HValue* original_value,
Representation representation,
int32_t new_offset) {
HConstant* new_constant = new(BasicBlock()->zone())
HConstant(Handle<Object>(Smi::FromInt(new_offset)),
Representation::Integer32());
if (*add == NULL) {
new_constant->InsertBefore(Check());
*add = new(BasicBlock()->zone()) HAdd(NULL,
original_value,
new_constant);
(*add)->AssumeRepresentation(representation);
(*add)->InsertBefore(Check());
} else {
new_constant->InsertBefore(*add);
(*constant)->DeleteAndReplaceWith(new_constant);
}
*constant = new_constant;
}
void RemoveZeroAdd(HAdd** add, HConstant** constant) {
if (*add != NULL && (*constant)->Integer32Value() == 0) {
(*add)->DeleteAndReplaceWith((*add)->left());
(*constant)->DeleteAndReplaceWith(NULL);
}
}
};
static bool BoundsCheckKeyMatch(void* key1, void* key2) {
BoundsCheckKey* k1 = static_cast<BoundsCheckKey*>(key1);
BoundsCheckKey* k2 = static_cast<BoundsCheckKey*>(key2);
return k1->IndexBase() == k2->IndexBase() && k1->Length() == k2->Length();
}
class BoundsCheckTable : private ZoneHashMap {
public:
BoundsCheckBbData** LookupOrInsert(BoundsCheckKey* key) {
return reinterpret_cast<BoundsCheckBbData**>(
&(Lookup(key, key->Hash(), true)->value));
}
void Insert(BoundsCheckKey* key, BoundsCheckBbData* data) {
Lookup(key, key->Hash(), true)->value = data;
}
void Delete(BoundsCheckKey* key) {
Remove(key, key->Hash());
}
BoundsCheckTable() : ZoneHashMap(BoundsCheckKeyMatch) { }
};
// Eliminates checks in bb and recursively in the dominated blocks.
// Also replace the results of check instructions with the original value, if
// the result is used. This is safe now, since we don't do code motion after
// this point. It enables better register allocation since the value produced
// by check instructions is really a copy of the original value.
void HGraph::EliminateRedundantBoundsChecks(HBasicBlock* bb,
BoundsCheckTable* table) {
BoundsCheckBbData* bb_data_list = NULL;
for (HInstruction* i = bb->first(); i != NULL; i = i->next()) {
if (!i->IsBoundsCheck()) continue;
HBoundsCheck* check = HBoundsCheck::cast(i);
check->ReplaceAllUsesWith(check->index());
isolate()->counters()->array_bounds_checks_seen()->Increment();
if (!FLAG_array_bounds_checks_elimination) continue;
int32_t offset;
BoundsCheckKey* key =
BoundsCheckKey::Create(bb->zone(), check, &offset);
BoundsCheckBbData** data_p = table->LookupOrInsert(key);
BoundsCheckBbData* data = *data_p;
if (data == NULL) {
bb_data_list = new(zone()) BoundsCheckBbData(key,
offset,
offset,
bb,
check,
bb_data_list,
NULL);
*data_p = bb_data_list;
} else if (data->OffsetIsCovered(offset)) {
check->DeleteAndReplaceWith(NULL);
isolate()->counters()->array_bounds_checks_removed()->Increment();
} else if (data->BasicBlock() == bb) {
data->CoverCheck(check, offset);
isolate()->counters()->array_bounds_checks_removed()->Increment();
} else {
int32_t new_lower_offset = offset < data->LowerOffset()
? offset
: data->LowerOffset();
int32_t new_upper_offset = offset > data->UpperOffset()
? offset
: data->UpperOffset();
bb_data_list = new(bb->zone()) BoundsCheckBbData(key,
new_lower_offset,
new_upper_offset,
bb,
check,
bb_data_list,
data);
table->Insert(key, bb_data_list);
}
}
for (int i = 0; i < bb->dominated_blocks()->length(); ++i) {
EliminateRedundantBoundsChecks(bb->dominated_blocks()->at(i), table);
}
for (BoundsCheckBbData* data = bb_data_list;
data != NULL;
data = data->NextInBasicBlock()) {
data->RemoveZeroOperations();
if (data->FatherInDominatorTree()) {
table->Insert(data->Key(), data->FatherInDominatorTree());
} else {
table->Delete(data->Key());
}
}
}
void HGraph::EliminateRedundantBoundsChecks() {
HPhase phase("H_Eliminate bounds checks", this);
AssertNoAllocation no_gc;
BoundsCheckTable checks_table;
EliminateRedundantBoundsChecks(entry_block(), &checks_table);
}
......
......@@ -241,7 +241,7 @@ class HLoopInformation: public ZoneObject {
HStackCheck* stack_check_;
};
class BoundsCheckTable;
class HGraph: public ZoneObject {
public:
explicit HGraph(CompilationInfo* info);
......@@ -266,6 +266,7 @@ class HGraph: public ZoneObject {
void OrderBlocks();
void AssignDominators();
void ReplaceCheckedValues();
void EliminateRedundantBoundsChecks();
void PropagateDeoptimizingMark();
// Returns false if there are phi-uses of the arguments-object
......@@ -358,6 +359,7 @@ class HGraph: public ZoneObject {
void InferTypes(ZoneList<HValue*>* worklist);
void InitializeInferredTypes(int from_inclusive, int to_inclusive);
void CheckForBackEdge(HBasicBlock* block, HBasicBlock* successor);
void EliminateRedundantBoundsChecks(HBasicBlock* bb, BoundsCheckTable* table);
Isolate* isolate_;
int next_block_id_;
......
......@@ -236,6 +236,8 @@ namespace internal {
SC(math_sin, V8.MathSin) \
SC(math_sqrt, V8.MathSqrt) \
SC(math_tan, V8.MathTan) \
SC(array_bounds_checks_seen, V8.ArrayBoundsChecksSeen) \
SC(array_bounds_checks_removed, V8.ArrayBoundsChecksRemoved) \
SC(transcendental_cache_hit, V8.TranscendentalCacheHit) \
SC(transcendental_cache_miss, V8.TranscendentalCacheMiss) \
SC(stack_interrupts, V8.StackInterrupts) \
......
// 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 --expose-gc
var a = new Int32Array(1024);
function test_base(base,cond) {
a[base + 1] = 1;
a[base + 4] = 2;
a[base + 3] = 3;
a[base + 2] = 4;
a[base + 4] = base + 4;
if (cond) {
a[base + 1] = 1;
a[base + 2] = 2;
a[base + 2] = 3;
a[base + 2] = 4;
a[base + 4] = base + 4;
} else {
a[base + 6] = 1;
a[base + 4] = 2;
a[base + 3] = 3;
a[base + 2] = 4;
a[base + 4] = base - 4;
}
}
function check_test_base(base,cond) {
if (cond) {
assertEquals(1, a[base + 1]);
assertEquals(4, a[base + 2]);
assertEquals(base + 4, a[base + 4]);
} else {
assertEquals(1, a[base + 6]);
assertEquals(3, a[base + 3]);
assertEquals(4, a[base + 2]);
assertEquals(base - 4, a[base + 4]);
}
}
function test_minus(base,cond) {
a[base - 1] = 1;
a[base - 2] = 2;
a[base + 4] = 3;
a[base] = 4;
a[base + 4] = base + 4;
if (cond) {
a[base - 4] = 1;
a[base + 5] = 2;
a[base + 3] = 3;
a[base + 2] = 4;
a[base + 4] = base + 4;
} else {
a[base + 6] = 1;
a[base + 4] = 2;
a[base + 3] = 3;
a[base + 2] = 4;
a[base + 4] = base - 4;
}
}
function check_test_minus(base,cond) {
if (cond) {
assertEquals(2, a[base + 5]);
assertEquals(3, a[base + 3]);
assertEquals(4, a[base + 2]);
assertEquals(base + 4, a[base + 4]);
} else {
assertEquals(1, a[base + 6]);
assertEquals(3, a[base + 3]);
assertEquals(4, a[base + 2]);
assertEquals(base - 4, a[base + 4]);
}
}
test_base(1,true);
test_base(2,true);
test_base(1,false);
test_base(2,false);
%OptimizeFunctionOnNextCall(test_base);
test_base(3,true);
check_test_base(3,true);
test_base(3,false);
check_test_base(3,false);
test_minus(5,true);
test_minus(6,true);
%OptimizeFunctionOnNextCall(test_minus);
test_minus(7,true);
check_test_minus(7,true);
test_minus(7,false);
check_test_minus(7,false);
// Optimization status:
// YES: 1
// NO: 2
// ALWAYS: 3
// NEVER: 4
if (false) {
test_base(5,true);
test_base(6,true);
test_base(5,false);
test_base(6,false);
%OptimizeFunctionOnNextCall(test_base);
test_base(-2,true);
assertTrue(%GetOptimizationStatus(test_base) != 1);
test_base(5,true);
test_base(6,true);
test_base(5,false);
test_base(6,false);
%OptimizeFunctionOnNextCall(test_base);
test_base(2048,true);
assertTrue(%GetOptimizationStatus(test_base) != 1);
}
gc();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment