| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| */ |
| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "jit/IonAnalysis.h" |
| #include "jit/MIRGenerator.h" |
| #include "jit/MIRGraph.h" |
| #include "jit/RangeAnalysis.h" |
| #include "jit/ValueNumbering.h" |
| |
| #include "jsapi-tests/testJitMinimalFunc.h" |
| #include "jsapi-tests/tests.h" |
| |
| using namespace js; |
| using namespace js::jit; |
| |
| static MBasicBlock* |
| FollowTrivialGotos(MBasicBlock* block) |
| { |
| while (block->phisEmpty() && *block->begin() == block->lastIns() && block->lastIns()->isGoto()) |
| block = block->lastIns()->toGoto()->getSuccessor(0); |
| return block; |
| } |
| |
| BEGIN_TEST(testJitGVN_FixupOSROnlyLoop) |
| { |
| // This is a testcase which constructs the very rare circumstances that |
| // require the FixupOSROnlyLoop logic. |
| |
| MinimalFunc func; |
| |
| MBasicBlock* entry = func.createEntryBlock(); |
| MBasicBlock* osrEntry = func.createOsrEntryBlock(); |
| MBasicBlock* outerHeader = func.createBlock(entry); |
| MBasicBlock* merge = func.createBlock(outerHeader); |
| MBasicBlock* innerHeader = func.createBlock(merge); |
| MBasicBlock* innerBackedge = func.createBlock(innerHeader); |
| MBasicBlock* outerBackedge = func.createBlock(innerHeader); |
| MBasicBlock* exit = func.createBlock(outerHeader); |
| |
| MConstant* c = MConstant::New(func.alloc, BooleanValue(false)); |
| entry->add(c); |
| entry->end(MTest::New(func.alloc, c, outerHeader, exit)); |
| osrEntry->end(MGoto::New(func.alloc, merge)); |
| |
| merge->end(MGoto::New(func.alloc, innerHeader)); |
| |
| // Use Beta nodes to hide the constants and suppress folding. |
| MConstant* x = MConstant::New(func.alloc, BooleanValue(false)); |
| outerHeader->add(x); |
| MBeta* xBeta = MBeta::New(func.alloc, x, Range::NewInt32Range(func.alloc, 0, 1)); |
| outerHeader->add(xBeta); |
| outerHeader->end(MTest::New(func.alloc, xBeta, merge, exit)); |
| |
| MConstant* y = MConstant::New(func.alloc, BooleanValue(false)); |
| innerHeader->add(y); |
| MBeta* yBeta = MBeta::New(func.alloc, y, Range::NewInt32Range(func.alloc, 0, 1)); |
| innerHeader->add(yBeta); |
| innerHeader->end(MTest::New(func.alloc, yBeta, innerBackedge, outerBackedge)); |
| |
| innerBackedge->end(MGoto::New(func.alloc, innerHeader)); |
| outerBackedge->end(MGoto::New(func.alloc, outerHeader)); |
| |
| MConstant* u = MConstant::New(func.alloc, UndefinedValue()); |
| exit->add(u); |
| exit->end(MReturn::New(func.alloc, u)); |
| |
| innerHeader->addPredecessorWithoutPhis(innerBackedge); |
| outerHeader->addPredecessorWithoutPhis(outerBackedge); |
| exit->addPredecessorWithoutPhis(entry); |
| merge->addPredecessorWithoutPhis(osrEntry); |
| |
| outerHeader->setLoopHeader(outerBackedge); |
| innerHeader->setLoopHeader(innerBackedge); |
| |
| if (!func.runGVN()) |
| return false; |
| |
| // The loops are no longer reachable from the normal entry. They are |
| // doinated by the osrEntry. |
| MOZ_RELEASE_ASSERT(func.graph.osrBlock() == osrEntry); |
| MBasicBlock* newInner = FollowTrivialGotos(osrEntry->lastIns()->toGoto()->target()); |
| MBasicBlock* newOuter = FollowTrivialGotos(newInner->lastIns()->toTest()->ifFalse()); |
| MBasicBlock* newExit = FollowTrivialGotos(entry); |
| MOZ_RELEASE_ASSERT(newInner->isLoopHeader()); |
| MOZ_RELEASE_ASSERT(newOuter->isLoopHeader()); |
| MOZ_RELEASE_ASSERT(newExit->lastIns()->isReturn()); |
| |
| // One more time. |
| ClearDominatorTree(func.graph); |
| if (!func.runGVN()) |
| return false; |
| |
| // The loops are no longer reachable from the normal entry. They are |
| // doinated by the osrEntry. |
| MOZ_RELEASE_ASSERT(func.graph.osrBlock() == osrEntry); |
| newInner = FollowTrivialGotos(osrEntry->lastIns()->toGoto()->target()); |
| newOuter = FollowTrivialGotos(newInner->lastIns()->toTest()->ifFalse()); |
| newExit = FollowTrivialGotos(entry); |
| MOZ_RELEASE_ASSERT(newInner->isLoopHeader()); |
| MOZ_RELEASE_ASSERT(newOuter->isLoopHeader()); |
| MOZ_RELEASE_ASSERT(newExit->lastIns()->isReturn()); |
| |
| return true; |
| } |
| END_TEST(testJitGVN_FixupOSROnlyLoop) |
| |
| BEGIN_TEST(testJitGVN_FixupOSROnlyLoopNested) |
| { |
| // Same as testJitGVN_FixupOSROnlyLoop but adds another level of loop |
| // nesting for added excitement. |
| |
| MinimalFunc func; |
| |
| MBasicBlock* entry = func.createEntryBlock(); |
| MBasicBlock* osrEntry = func.createOsrEntryBlock(); |
| MBasicBlock* outerHeader = func.createBlock(entry); |
| MBasicBlock* middleHeader = func.createBlock(outerHeader); |
| MBasicBlock* merge = func.createBlock(middleHeader); |
| MBasicBlock* innerHeader = func.createBlock(merge); |
| MBasicBlock* innerBackedge = func.createBlock(innerHeader); |
| MBasicBlock* middleBackedge = func.createBlock(innerHeader); |
| MBasicBlock* outerBackedge = func.createBlock(middleHeader); |
| MBasicBlock* exit = func.createBlock(outerHeader); |
| |
| MConstant* c = MConstant::New(func.alloc, BooleanValue(false)); |
| entry->add(c); |
| entry->end(MTest::New(func.alloc, c, outerHeader, exit)); |
| osrEntry->end(MGoto::New(func.alloc, merge)); |
| |
| merge->end(MGoto::New(func.alloc, innerHeader)); |
| |
| // Use Beta nodes to hide the constants and suppress folding. |
| MConstant* x = MConstant::New(func.alloc, BooleanValue(false)); |
| outerHeader->add(x); |
| MBeta* xBeta = MBeta::New(func.alloc, x, Range::NewInt32Range(func.alloc, 0, 1)); |
| outerHeader->add(xBeta); |
| outerHeader->end(MTest::New(func.alloc, xBeta, middleHeader, exit)); |
| |
| MConstant* y = MConstant::New(func.alloc, BooleanValue(false)); |
| middleHeader->add(y); |
| MBeta* yBeta = MBeta::New(func.alloc, y, Range::NewInt32Range(func.alloc, 0, 1)); |
| middleHeader->add(yBeta); |
| middleHeader->end(MTest::New(func.alloc, yBeta, merge, outerBackedge)); |
| |
| MConstant* w = MConstant::New(func.alloc, BooleanValue(false)); |
| innerHeader->add(w); |
| MBeta* wBeta = MBeta::New(func.alloc, w, Range::NewInt32Range(func.alloc, 0, 1)); |
| innerHeader->add(wBeta); |
| innerHeader->end(MTest::New(func.alloc, wBeta, innerBackedge, middleBackedge)); |
| |
| innerBackedge->end(MGoto::New(func.alloc, innerHeader)); |
| middleBackedge->end(MGoto::New(func.alloc, middleHeader)); |
| outerBackedge->end(MGoto::New(func.alloc, outerHeader)); |
| |
| MConstant* u = MConstant::New(func.alloc, UndefinedValue()); |
| exit->add(u); |
| exit->end(MReturn::New(func.alloc, u)); |
| |
| innerHeader->addPredecessorWithoutPhis(innerBackedge); |
| middleHeader->addPredecessorWithoutPhis(middleBackedge); |
| outerHeader->addPredecessorWithoutPhis(outerBackedge); |
| exit->addPredecessorWithoutPhis(entry); |
| merge->addPredecessorWithoutPhis(osrEntry); |
| |
| outerHeader->setLoopHeader(outerBackedge); |
| middleHeader->setLoopHeader(middleBackedge); |
| innerHeader->setLoopHeader(innerBackedge); |
| |
| if (!func.runGVN()) |
| return false; |
| |
| // The loops are no longer reachable from the normal entry. They are |
| // doinated by the osrEntry. |
| MOZ_RELEASE_ASSERT(func.graph.osrBlock() == osrEntry); |
| MBasicBlock* newInner = FollowTrivialGotos(osrEntry->lastIns()->toGoto()->target()); |
| MBasicBlock* newMiddle = FollowTrivialGotos(newInner->lastIns()->toTest()->ifFalse()); |
| MBasicBlock* newOuter = FollowTrivialGotos(newMiddle->lastIns()->toTest()->ifFalse()); |
| MBasicBlock* newExit = FollowTrivialGotos(entry); |
| MOZ_RELEASE_ASSERT(newInner->isLoopHeader()); |
| MOZ_RELEASE_ASSERT(newMiddle->isLoopHeader()); |
| MOZ_RELEASE_ASSERT(newOuter->isLoopHeader()); |
| MOZ_RELEASE_ASSERT(newExit->lastIns()->isReturn()); |
| |
| // One more time. |
| ClearDominatorTree(func.graph); |
| if (!func.runGVN()) |
| return false; |
| |
| // The loops are no longer reachable from the normal entry. They are |
| // doinated by the osrEntry. |
| MOZ_RELEASE_ASSERT(func.graph.osrBlock() == osrEntry); |
| newInner = FollowTrivialGotos(osrEntry->lastIns()->toGoto()->target()); |
| newMiddle = FollowTrivialGotos(newInner->lastIns()->toTest()->ifFalse()); |
| newOuter = FollowTrivialGotos(newMiddle->lastIns()->toTest()->ifFalse()); |
| newExit = FollowTrivialGotos(entry); |
| MOZ_RELEASE_ASSERT(newInner->isLoopHeader()); |
| MOZ_RELEASE_ASSERT(newMiddle->isLoopHeader()); |
| MOZ_RELEASE_ASSERT(newOuter->isLoopHeader()); |
| MOZ_RELEASE_ASSERT(newExit->lastIns()->isReturn()); |
| |
| return true; |
| } |
| END_TEST(testJitGVN_FixupOSROnlyLoopNested) |
| |
| BEGIN_TEST(testJitGVN_PinnedPhis) |
| { |
| // Set up a loop which gets optimized away, with phis which must be |
| // cleaned up, permitting more phis to be cleaned up. |
| |
| MinimalFunc func; |
| |
| MBasicBlock* entry = func.createEntryBlock(); |
| MBasicBlock* outerHeader = func.createBlock(entry); |
| MBasicBlock* outerBlock = func.createBlock(outerHeader); |
| MBasicBlock* innerHeader = func.createBlock(outerBlock); |
| MBasicBlock* innerBackedge = func.createBlock(innerHeader); |
| MBasicBlock* exit = func.createBlock(innerHeader); |
| |
| MPhi* phi0 = MPhi::New(func.alloc); |
| MPhi* phi1 = MPhi::New(func.alloc); |
| MPhi* phi2 = MPhi::New(func.alloc); |
| MPhi* phi3 = MPhi::New(func.alloc); |
| |
| MParameter* p = func.createParameter(); |
| entry->add(p); |
| MConstant* z0 = MConstant::New(func.alloc, Int32Value(0)); |
| MConstant* z1 = MConstant::New(func.alloc, Int32Value(1)); |
| MConstant* z2 = MConstant::New(func.alloc, Int32Value(2)); |
| MConstant* z3 = MConstant::New(func.alloc, Int32Value(2)); |
| phi0->addInputSlow(z0); |
| phi1->addInputSlow(z1); |
| phi2->addInputSlow(z2); |
| phi3->addInputSlow(z3); |
| entry->add(z0); |
| entry->add(z1); |
| entry->add(z2); |
| entry->add(z3); |
| entry->end(MGoto::New(func.alloc, outerHeader)); |
| |
| outerHeader->addPhi(phi0); |
| outerHeader->addPhi(phi1); |
| outerHeader->addPhi(phi2); |
| outerHeader->addPhi(phi3); |
| outerHeader->end(MGoto::New(func.alloc, outerBlock)); |
| |
| outerBlock->end(MGoto::New(func.alloc, innerHeader)); |
| |
| MConstant* true_ = MConstant::New(func.alloc, BooleanValue(true)); |
| innerHeader->add(true_); |
| innerHeader->end(MTest::New(func.alloc, true_, innerBackedge, exit)); |
| |
| innerBackedge->end(MGoto::New(func.alloc, innerHeader)); |
| |
| MInstruction* z4 = MAdd::New(func.alloc, phi0, phi1); |
| MConstant* z5 = MConstant::New(func.alloc, Int32Value(4)); |
| MInstruction* z6 = MAdd::New(func.alloc, phi2, phi3); |
| MConstant* z7 = MConstant::New(func.alloc, Int32Value(6)); |
| phi0->addInputSlow(z4); |
| phi1->addInputSlow(z5); |
| phi2->addInputSlow(z6); |
| phi3->addInputSlow(z7); |
| exit->add(z4); |
| exit->add(z5); |
| exit->add(z6); |
| exit->add(z7); |
| exit->end(MGoto::New(func.alloc, outerHeader)); |
| |
| innerHeader->addPredecessorWithoutPhis(innerBackedge); |
| outerHeader->addPredecessorWithoutPhis(exit); |
| |
| outerHeader->setLoopHeader(exit); |
| innerHeader->setLoopHeader(innerBackedge); |
| |
| if (!func.runGVN()) |
| return false; |
| |
| MOZ_RELEASE_ASSERT(innerHeader->phisEmpty()); |
| MOZ_RELEASE_ASSERT(exit->isDead()); |
| |
| return true; |
| } |
| END_TEST(testJitGVN_PinnedPhis) |