TEST(Simplifier, CondJmp) { IRUnit unit{test_context}; Simplifier sim{unit}; BCMarker marker = BCMarker::Dummy(); // Folding Conv*ToBool { auto val = unit.gen(Conjure, marker, Type::Int); auto cnv = unit.gen(ConvIntToBool, marker, val->dst()); auto jcc = unit.gen(JmpZero, marker, unit.defBlock(), cnv->dst()); auto result = sim.simplify(jcc, false); EXPECT_EQ(nullptr, result.dst); ASSERT_EQ(1, result.instrs.size()); EXPECT_MATCH(result.instrs[0], JmpZero, val->dst()); } // Folding in negation { auto val = unit.gen(Conjure, marker, Type::Bool); auto neg = unit.gen(XorBool, marker, val->dst(), unit.cns(true)); auto jcc = unit.gen(JmpZero, marker, unit.defBlock(), neg->dst()); auto result = sim.simplify(jcc, false); EXPECT_EQ(nullptr, result.dst); ASSERT_EQ(1, result.instrs.size()); EXPECT_MATCH(result.instrs[0], JmpNZero, val->dst()); } }
TEST(Simplifier, DoubleCmp) { IRUnit unit{test_context}; BCMarker dummy = BCMarker::Dummy(); // Lt(X:Dbl, Y:Int) --> LtDbl(X, ConvIntToDbl(Y)) { auto x = unit.gen(Conjure, dummy, Type::Dbl); auto y = unit.gen(Conjure, dummy, Type::Int); auto lt = unit.gen(Lt, dummy, x->dst(), y->dst()); auto result = simplify(unit, lt, false); auto conv = result.instrs[0]; EXPECT_MATCH(conv, ConvIntToDbl, y->dst()); EXPECT_MATCH(result.instrs[1], LtDbl, x->dst(), conv->dst()); EXPECT_EQ(result.instrs[1]->dst(), result.dst); } // Lt(X:Dbl, 10) --> LtDbl(X, 10.0) { auto x = unit.gen(Conjure, dummy, Type::Dbl); auto lt = unit.gen(Lt, dummy, x->dst(), unit.cns(10)); auto result = simplify(unit, lt, false); EXPECT_MATCH(result.instrs[0], LtDbl, x->dst(), unit.cns(10.0)); } }
TEST(Simplifier, Count) { IRUnit unit{test_context}; Simplifier sim{unit}; BCMarker dummy = BCMarker::Dummy(); // Count($null) --> 0 { auto null = unit.gen(Conjure, dummy, Type::Null); auto count = unit.gen(Count, dummy, null->dst()); auto result = sim.simplify(count, false); EXPECT_NE(nullptr, result.dst); EXPECT_EQ(0, result.instrs.size()); EXPECT_EQ(0, result.dst->intVal()); } // Count($bool_int_dbl_str) --> 1 { auto ty = Type::Bool | Type::Int | Type::Dbl | Type::Str | Type::Res; auto val = unit.gen(Conjure, dummy, ty); auto count = unit.gen(Count, dummy, val->dst()); auto result = sim.simplify(count, false); EXPECT_NE(nullptr, result.dst); EXPECT_EQ(0, result.instrs.size()); EXPECT_EQ(1, result.dst->intVal()); } // Count($array_no_kind) --> CountArray($array_no_kind) { auto arr = unit.gen(Conjure, dummy, Type::Arr); auto count = unit.gen(Count, dummy, arr->dst()); auto result = sim.simplify(count, false); EXPECT_NE(nullptr, result.dst); EXPECT_EQ(1, result.instrs.size()); EXPECT_MATCH(result.instrs[0], CountArray, arr->dst()); } // Count($array_not_nvtw) --> CountArrayFast($array_not_nvtw) { auto ty = Type::Arr.specialize(ArrayData::kPackedKind); auto arr = unit.gen(Conjure, dummy, ty); auto count = unit.gen(Count, dummy, arr->dst()); auto result = sim.simplify(count, false); EXPECT_NE(nullptr, result.dst); EXPECT_EQ(1, result.instrs.size()); EXPECT_MATCH(result.instrs[0], CountArrayFast, arr->dst()); } // Count($some_obj) --> Count($some_obj) { auto obj = unit.gen(Conjure, dummy, Type::Obj); auto count = unit.gen(Count, dummy, obj->dst()); auto result = sim.simplify(count, false); EXPECT_NO_CHANGE(result); } }
TEST(Simplifier, JumpFuse) { BCMarker dummy = BCMarker::Dummy(); IRUnit unit(test_context); Simplifier sim(unit); { // JmpZero(Eq(X, true)) --> JmpEq(X, false) auto taken = unit.defBlock(); auto lhs = unit.cns(true); auto rhs = unit.gen(Conjure, dummy, Type::Bool); auto eq = unit.gen(Eq, dummy, lhs, rhs->dst()); auto jmp = unit.gen(JmpZero, dummy, taken, eq->dst()); auto result = sim.simplify(jmp, false); EXPECT_EQ(nullptr, result.dst); EXPECT_EQ(2, result.instrs.size()); // This is a dead Eq instruction; an artifact of weirdness in the // implementation. Should go away. EXPECT_FALSE(result.instrs[0]->isControlFlow()); EXPECT_MATCH(result.instrs[1], JmpEq, taken, rhs->dst(), unit.cns(false)); } { // JmpNZero(Neq(X:Int, Y:Int)) --> JmpNeqInt(X, Y) auto taken = unit.defBlock(); auto x = unit.gen(Conjure, dummy, Type::Int); auto y = unit.gen(Conjure, dummy, Type::Int); auto neq = unit.gen(Neq, dummy, x->dst(), y->dst()); auto jmp = unit.gen(JmpNZero, dummy, taken, neq->dst()); auto result = sim.simplify(jmp, false); EXPECT_EQ(nullptr, result.dst); EXPECT_EQ(2, result.instrs.size()); EXPECT_FALSE(result.instrs[0]->isControlFlow()); // dead Neq EXPECT_MATCH(result.instrs[1], JmpNeqInt, taken, x->dst(), y->dst()); } { // JmpNZero(Neq(X:Cls, Y:Cls)) --> JmpNeq(X, Y) auto taken = unit.defBlock(); auto x = unit.gen(Conjure, dummy, Type::Bool); auto y = unit.gen(Conjure, dummy, Type::Bool); auto neq = unit.gen(Neq, dummy, x->dst(), y->dst()); auto jmp = unit.gen(JmpNZero, dummy, taken, neq->dst()); auto result = sim.simplify(jmp, false); EXPECT_EQ(nullptr, result.dst); EXPECT_EQ(1, result.instrs.size()); EXPECT_MATCH(result.instrs[0], JmpNeq, taken, x->dst(), y->dst()); } }
TEST(PredictionOpts, basic) { UNUSED auto const bcctx = BCContext { BCMarker::Dummy(), 0 }; IRUnit unit{test_context}; Block* entry = unit.entry(); Block* taken = unit.defBlock(); Block* end = unit.defBlock(); auto ptr = unit.gen(Conjure, bcctx, TPtrToGen); auto ldm = unit.gen(LdMem, bcctx, TGen, ptr->dst()); auto inc = unit.gen(IncRef, bcctx, ldm->dst()); auto ckt = unit.gen(CheckType, bcctx, TInt, taken, ldm->dst()); ckt->setNext(end); entry->push_back({ptr, ldm, inc, ckt}); taken->push_back(unit.gen(Halt, bcctx)); end->push_back(unit.gen(Halt, bcctx)); optimizePredictions(unit); // It should have pushed LdMem and IncRef to each successor block, with the // narrowed type on the fallthrough block. { ASSERT_EQ(2, entry->instrs().size()); EXPECT_MATCH(entry->back(), CheckTypeMem, TInt, taken); EXPECT_EQ(end, entry->back().next()); } { ASSERT_EQ(3, taken->instrs().size()); auto takenIt = taken->begin(); auto& ldmem = *takenIt; auto& incref = *(++takenIt); EXPECT_MATCH(ldmem, LdMem, TGen, ptr->dst()); EXPECT_MATCH(incref, IncRef, ldmem.dst()); } { ASSERT_EQ(4, end->instrs().size()); auto endIt = end->begin(); auto& ldmem = *endIt; auto& incref = *(++endIt); auto& mov = *(++endIt); EXPECT_MATCH(ldmem, LdMem, TInt, ptr->dst()); EXPECT_MATCH(incref, IncRef, ldmem.dst()); EXPECT_MATCH(mov, Mov, ldmem.dst()); EXPECT_EQ(ckt->dst(), mov.dst()); } }
TEST(JumpOpts, optimizeCondTraceExit) { BCMarker marker = BCMarker::Dummy(); IRUnit unit{test_context}; Block* entry = unit.entry(); Block* taken = unit.defBlock(); Block* fallthru = unit.defBlock(); // A conditional jump that goes to "SyncABIRegs; ReqBindJmp" on both edges // can be coalesced into a ReqBindJmpSomething. auto fp = unit.gen(DefFP, marker); auto sp = unit.gen(DefSP, marker, StackOffset(0), fp->dst()); auto val = unit.gen(Conjure, marker, Type::Bool); auto jmp = unit.gen(JmpZero, marker, taken, val->dst()); jmp->setNext(fallthru); entry->push_back({fp, sp, val, jmp}); auto bcoff1 = 10; auto sync1 = unit.gen(SyncABIRegs, marker, fp->dst(), sp->dst()); auto bind1 = unit.gen(ReqBindJmp, marker, ReqBindJmpData(bcoff1)); taken->push_back({sync1, bind1}); auto bcoff2 = 20; auto sync2 = unit.gen(SyncABIRegs, marker, fp->dst(), sp->dst()); auto bind2 = unit.gen(ReqBindJmp, marker, ReqBindJmpData(bcoff2)); fallthru->push_back({sync2, bind2}); optimizeJumps(unit); EXPECT_EQ(nullptr, entry->next()); EXPECT_EQ(nullptr, entry->taken()); auto const& back = entry->back(); auto const* data = back.extra<ReqBindJccData>(); EXPECT_MATCH(back, ReqBindJmpZero, val->dst()); EXPECT_EQ(bcoff1, data->taken); EXPECT_EQ(bcoff2, data->notTaken); }
TEST(JumpOpts, optimizeSideExitCheck) { BCMarker marker = BCMarker::Dummy(); IRUnit unit{test_context}; Block* entry = unit.entry(); Block* taken = unit.defBlock(); Block* fallthru = unit.defBlock(); // A conditional jump that goes to a "SyncABIRegs; ReqBindJmp" on the taken // edge only can turn into a SideExitJmpSomething. auto fp = unit.gen(DefFP, marker); auto sp = unit.gen(DefSP, marker, StackOffset(0), fp->dst()); auto chk = unit.gen(CheckStk, marker, Type::Int, taken, StackOffset(0), sp->dst()); chk->setNext(fallthru); entry->push_back({fp, sp, chk}); fallthru->push_back(unit.gen(Halt, marker)); auto bcoff = 10; auto sync = unit.gen(SyncABIRegs, marker, fp->dst(), sp->dst()); auto bind = unit.gen(ReqBindJmp, marker, BCOffset(bcoff)); taken->push_back({sync, bind}); optimizeJumps(unit); // This exercises trivial jump optimization too: since the JmpZero gets turned // into a non-branch, the entry block gets coalesced with the Halt block. EXPECT_EQ(nullptr, entry->next()); EXPECT_EQ(nullptr, entry->taken()); EXPECT_EQ(Halt, entry->back().op()); auto const& sideExit = *(--entry->backIter()); EXPECT_MATCH(sideExit, SideExitGuardStk); EXPECT_EQ(bcoff, sideExit.extra<SideExitGuardData>()->taken); }
TEST(JumpOpts, eliminateTrivial) { BCMarker marker = BCMarker::Dummy(); // Trivial jumps are eliminated { IRUnit unit{test_context}; Block* entry = unit.entry(); Block* second = unit.defBlock(); entry->push_back(unit.gen(Jmp, marker, second)); // Make sure the DefLabel gets deleted second->push_back(unit.gen(DefLabel, marker)); second->push_back(unit.gen(Halt, marker)); optimizeJumps(unit); EXPECT_EQ(1, entry->instrs().size()); EXPECT_MATCH(entry->front(), Halt); EXPECT_TRUE(entry->isExit()); } // Jumps with arguments are also eliminated { IRUnit unit{test_context}; Block* entry = unit.entry(); Block* second = unit.defBlock(); auto value = unit.gen(Conjure, marker, Type::Gen); entry->push_back(value); entry->push_back(unit.gen(Jmp, marker, second, value->dst())); second->push_back(unit.defLabel(1, marker, {0})); second->push_back(unit.gen(Halt, marker)); optimizeJumps(unit); EXPECT_EQ(3, entry->instrs().size()); EXPECT_MATCH(entry->back(), Halt); EXPECT_TRUE(entry->isExit()); } // Jumps to blocks with other predecessors are not eliminated { IRUnit unit{test_context}; Block* pred1 = unit.entry(); Block* pred2 = unit.defBlock(); Block* succ = unit.defBlock(); // pred2 is actually unreachable but this optimization pass shouldn't be // concerned about that pred1->push_back(unit.gen(Jmp, marker, succ)); pred2->push_back(unit.gen(Jmp, marker, succ)); succ->push_back(unit.gen(Halt, marker)); optimizeJumps(unit); EXPECT_EQ(1, pred1->instrs().size()); EXPECT_EQ(1, pred2->instrs().size()); EXPECT_MATCH(pred1->back(), Jmp, succ); EXPECT_MATCH(pred2->back(), Jmp, succ); } }