diff --git a/src/hydrogen-instructions.cc b/src/hydrogen-instructions.cc index 4372c06f61..cdc3e233f2 100644 --- a/src/hydrogen-instructions.cc +++ b/src/hydrogen-instructions.cc @@ -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]"); + } } diff --git a/src/hydrogen-instructions.h b/src/hydrogen-instructions.h index b2b3a61830..39e3950251 100644 --- a/src/hydrogen-instructions.h +++ b/src/hydrogen-instructions.h @@ -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()); } diff --git a/src/hydrogen.cc b/src/hydrogen.cc index 857bbf9d37..7ec1f5bf95 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -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()); diff --git a/src/hydrogen.h b/src/hydrogen.h index fc4b612104..47b569146b 100644 --- a/src/hydrogen.h +++ b/src/hydrogen.h @@ -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* predecessor_list_; + int current_; }; diff --git a/src/objects.cc b/src/objects.cc index e4f165ae44..324ecbb1b9 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -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; } diff --git a/src/objects.h b/src/objects.h index 76ac347d32..5063d3313d 100644 --- a/src/objects.h +++ b/src/objects.h @@ -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. diff --git a/test/mjsunit/elements-transition-hoisting.js b/test/mjsunit/elements-transition-hoisting.js new file mode 100644 index 0000000000..53dc940919 --- /dev/null +++ b/test/mjsunit/elements-transition-hoisting.js @@ -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)); +}