#include "jit/IonControlFlow.h"
#include "mozilla/DebugOnly.h"
using namespace js;
using namespace js::jit;
using mozilla::DebugOnly;
ControlFlowGenerator::ControlFlowGenerator(TempAllocator& temp,
JSScript* script)
: script(script),
current(nullptr),
pc(nullptr),
alloc_(temp),
blocks_(temp),
cfgStack_(temp),
loops_(temp),
switches_(temp),
labels_(temp),
aborted_(false),
checkedTryFinally_(false) {}
static inline int32_t GetJumpOffset(jsbytecode* pc) {
MOZ_ASSERT(CodeSpec[JSOp(*pc)].type() == JOF_JUMP);
return GET_JUMP_OFFSET(pc);
}
void ControlFlowGraph::dump(GenericPrinter& print, JSScript* script) {
if (blocks_.length() == 0) {
print.printf("Didn't run yet.\n");
return;
}
fprintf(stderr, "Dumping cfg:\n\n");
for (size_t i = 0; i < blocks_.length(); i++) {
print.printf(" Block %zu, %zu:%zu\n", blocks_[i].id(),
script->pcToOffset(blocks_[i].startPc()),
script->pcToOffset(blocks_[i].stopPc()));
jsbytecode* pc = blocks_[i].startPc();
for (; pc < blocks_[i].stopPc(); pc += CodeSpec[JSOp(*pc)].length) {
MOZ_ASSERT(pc < script->codeEnd());
print.printf(" %zu: %s\n", script->pcToOffset(pc), CodeName[JSOp(*pc)]);
}
if (blocks_[i].stopIns()->isGoto()) {
print.printf(" %s (popping:%zu) [", blocks_[i].stopIns()->Name(),
blocks_[i].stopIns()->toGoto()->popAmount());
} else {
print.printf(" %s [", blocks_[i].stopIns()->Name());
}
for (size_t j = 0; j < blocks_[i].stopIns()->numSuccessors(); j++) {
if (j != 0) {
print.printf(", ");
}
print.printf("%zu", blocks_[i].stopIns()->getSuccessor(j)->id());
}
print.printf("]\n\n");
}
}
bool ControlFlowGraph::init(TempAllocator& alloc,
const CFGBlockVector& blocks) {
if (!blocks_.reserve(blocks.length())) {
return false;
}
for (size_t i = 0; i < blocks.length(); i++) {
MOZ_ASSERT(blocks[i]->id() == i);
CFGBlock block(blocks[i]->startPc());
block.setStopPc(blocks[i]->stopPc());
block.setId(i);
blocks_.infallibleAppend(std::move(block));
}
for (size_t i = 0; i < blocks.length(); i++) {
if (!alloc.ensureBallast()) {
return false;
}
CFGControlInstruction* copy = nullptr;
CFGControlInstruction* ins = blocks[i]->stopIns();
switch (ins->type()) {
case CFGControlInstruction::Type_Goto: {
CFGBlock* successor = &blocks_[ins->getSuccessor(0)->id()];
copy = CFGGoto::CopyWithNewTargets(alloc, ins->toGoto(), successor);
break;
}
case CFGControlInstruction::Type_BackEdge: {
CFGBlock* successor = &blocks_[ins->getSuccessor(0)->id()];
copy = CFGBackEdge::CopyWithNewTargets(alloc, ins->toBackEdge(),
successor);
break;
}
case CFGControlInstruction::Type_LoopEntry: {
CFGLoopEntry* old = ins->toLoopEntry();
CFGBlock* successor = &blocks_[ins->getSuccessor(0)->id()];
copy = CFGLoopEntry::CopyWithNewTargets(alloc, old, successor);
break;
}
case CFGControlInstruction::Type_Throw: {
copy = CFGThrow::New(alloc);
break;
}
case CFGControlInstruction::Type_Test: {
CFGTest* old = ins->toTest();
CFGBlock* trueBranch = &blocks_[old->trueBranch()->id()];
CFGBlock* falseBranch = &blocks_[old->falseBranch()->id()];
copy = CFGTest::CopyWithNewTargets(alloc, old, trueBranch, falseBranch);
break;
}
case CFGControlInstruction::Type_CondSwitchCase: {
CFGCondSwitchCase* old = ins->toCondSwitchCase();
CFGBlock* trueBranch = &blocks_[old->trueBranch()->id()];
CFGBlock* falseBranch = &blocks_[old->falseBranch()->id()];
copy = CFGCondSwitchCase::CopyWithNewTargets(alloc, old, trueBranch,
falseBranch);
break;
}
case CFGControlInstruction::Type_Return: {
copy = CFGReturn::New(alloc);
break;
}
case CFGControlInstruction::Type_RetRVal: {
copy = CFGRetRVal::New(alloc);
break;
}
case CFGControlInstruction::Type_Try: {
CFGTry* old = ins->toTry();
CFGBlock* tryBlock = &blocks_[old->tryBlock()->id()];
CFGBlock* merge = nullptr;
if (old->numSuccessors() == 2) {
merge = &blocks_[old->afterTryCatchBlock()->id()];
}
copy = CFGTry::CopyWithNewTargets(alloc, old, tryBlock, merge);
break;
}
case CFGControlInstruction::Type_TableSwitch: {
CFGTableSwitch* old = ins->toTableSwitch();
CFGTableSwitch* tableSwitch =
CFGTableSwitch::New(alloc, old->low(), old->high());
if (!tableSwitch->addDefault(&blocks_[old->defaultCase()->id()])) {
return false;
}
for (size_t i = 0; i < ins->numSuccessors() - 1; i++) {
if (!tableSwitch->addCase(&blocks_[old->getCase(i)->id()])) {
return false;
}
}
copy = tableSwitch;
break;
}
}
MOZ_ASSERT(copy);
blocks_[i].setStopIns(copy);
}
return true;
}
bool ControlFlowGenerator::addBlock(CFGBlock* block) {
block->setId(blocks_.length());
return blocks_.append(block);
}
bool ControlFlowGenerator::traverseBytecode() {
blocks_.clear();
current = CFGBlock::New(alloc(), script->code());
pc = current->startPc();
if (!addBlock(current)) {
return false;
}
for (;;) {
MOZ_ASSERT(pc < script->codeEnd());
for (;;) {
if (!alloc().ensureBallast()) {
return false;
}
MOZ_ASSERT_IF(!cfgStack_.empty(), cfgStack_.back().stopAt >= pc);
if (!cfgStack_.empty() && cfgStack_.back().stopAt == pc) {
ControlStatus status = processCfgStack();
if (status == ControlStatus::Error) {
return false;
}
if (status == ControlStatus::Abort) {
aborted_ = true;
return false;
}
if (!current) {
return true;
}
continue;
}
ControlStatus status;
if ((status = snoopControlFlow(JSOp(*pc))) == ControlStatus::None) {
break;
}
if (status == ControlStatus::Error) {
return false;
}
if (status == ControlStatus::Abort) {
aborted_ = true;
return false;
}
if (!current) {
return true;
}
}
JSOp op = JSOp(*pc);
pc += CodeSpec[op].length;
}
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::snoopControlFlow(
JSOp op) {
switch (op) {
case JSOP_NOP: {
jssrcnote* sn = GetSrcNote(gsn, script, pc);
if (sn && SN_TYPE(sn) == SRC_FOR) {
return processForLoop(op, sn);
}
break;
}
case JSOP_RETURN:
case JSOP_RETRVAL:
return processReturn(op);
case JSOP_THROW:
return processThrow();
case JSOP_GOTO: {
jssrcnote* sn = GetSrcNote(gsn, script, pc);
switch (sn ? SN_TYPE(sn) : SRC_NULL) {
case SRC_BREAK:
case SRC_BREAK2LABEL:
return processBreak(op, sn);
case SRC_CONTINUE:
return processContinue(op);
case SRC_SWITCHBREAK:
return processSwitchBreak(op);
case SRC_WHILE:
case SRC_FOR_IN:
case SRC_FOR_OF:
return processWhileOrForInOrForOfLoop(sn);
default:
MOZ_CRASH("unknown goto case");
}
break;
}
case JSOP_LOOPHEAD: {
jssrcnote* sn = GetSrcNote(gsn, script, pc);
if (sn && SN_TYPE(sn) == SRC_DO_WHILE) {
return processDoWhileLoop(sn);
}
break;
}
case JSOP_TABLESWITCH: {
jssrcnote* sn = GetSrcNote(gsn, script, pc);
return processTableSwitch(op, sn);
}
case JSOP_CONDSWITCH:
return processCondSwitch();
case JSOP_IFNE:
MOZ_CRASH("we should never reach an ifne!");
case JSOP_IFEQ:
return processIfStart(JSOP_IFEQ);
case JSOP_AND:
case JSOP_OR:
return processAndOr(op);
case JSOP_LABEL:
return processLabel();
case JSOP_TRY:
return processTry();
case JSOP_THROWMSG:
return ControlStatus::Abort;
default:
break;
}
return ControlStatus::None;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processReturn(
JSOp op) {
MOZ_ASSERT(op == JSOP_RETURN || op == JSOP_RETRVAL);
CFGControlInstruction* ins;
if (op == JSOP_RETURN) {
ins = CFGReturn::New(alloc());
} else {
ins = CFGRetRVal::New(alloc());
}
endCurrentBlock(ins);
return processControlEnd();
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processThrow() {
CFGThrow* ins = CFGThrow::New(alloc());
endCurrentBlock(ins);
return processControlEnd();
}
void ControlFlowGenerator::endCurrentBlock(CFGControlInstruction* ins) {
current->setStopPc(pc);
current->setStopIns(ins);
current = nullptr;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processCfgStack() {
ControlStatus status = processCfgEntry(cfgStack_.back());
while (status == ControlStatus::Ended) {
popCfgStack();
if (cfgStack_.empty()) {
return status;
}
status = processCfgEntry(cfgStack_.back());
}
if (status == ControlStatus::Joined) {
popCfgStack();
}
return status;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processControlEnd() {
MOZ_ASSERT(!current);
if (cfgStack_.empty()) {
return ControlStatus::Ended;
}
return processCfgStack();
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processCfgEntry(
CFGState& state) {
switch (state.state) {
case CFGState::IF_TRUE:
case CFGState::IF_TRUE_EMPTY_ELSE:
return processIfEnd(state);
case CFGState::IF_ELSE_TRUE:
return processIfElseTrueEnd(state);
case CFGState::IF_ELSE_FALSE:
return processIfElseFalseEnd(state);
case CFGState::DO_WHILE_LOOP_BODY:
return processDoWhileBodyEnd(state);
case CFGState::DO_WHILE_LOOP_COND:
return processDoWhileCondEnd(state);
case CFGState::WHILE_LOOP_COND:
return processWhileCondEnd(state);
case CFGState::WHILE_LOOP_BODY:
return processWhileBodyEnd(state);
case CFGState::FOR_LOOP_COND:
return processForCondEnd(state);
case CFGState::FOR_LOOP_BODY:
return processForBodyEnd(state);
case CFGState::FOR_LOOP_UPDATE:
return processForUpdateEnd(state);
case CFGState::TABLE_SWITCH:
return processNextTableSwitchCase(state);
case CFGState::COND_SWITCH_CASE:
return processCondSwitchCase(state);
case CFGState::COND_SWITCH_BODY:
return processCondSwitchBody(state);
case CFGState::AND_OR:
return processAndOrEnd(state);
case CFGState::LABEL:
return processLabelEnd(state);
case CFGState::TRY:
return processTryEnd(state);
default:
MOZ_CRASH("unknown cfgstate");
}
}
void ControlFlowGenerator::popCfgStack() {
if (cfgStack_.back().isLoop()) {
loops_.popBack();
}
if (cfgStack_.back().state == CFGState::LABEL) {
labels_.popBack();
}
cfgStack_.popBack();
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processLabelEnd(
CFGState& state) {
MOZ_ASSERT(state.state == CFGState::LABEL);
if (!state.label.breaks && !current) {
return ControlStatus::Ended;
}
if (!state.label.breaks) {
return ControlStatus::Joined;
}
CFGBlock* successor = createBreakCatchBlock(state.label.breaks, state.stopAt);
if (!successor) {
return ControlStatus::Error;
}
if (current) {
current->setStopIns(CFGGoto::New(alloc(), successor));
current->setStopPc(pc);
}
current = successor;
pc = successor->startPc();
if (!addBlock(successor)) {
return ControlStatus::Error;
}
return ControlStatus::Joined;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processTry() {
MOZ_ASSERT(JSOp(*pc) == JSOP_TRY);
if (!checkedTryFinally_) {
for (const JSTryNote& tn : script->trynotes()) {
if (tn.kind == JSTRY_FINALLY) {
return ControlStatus::Abort;
}
}
checkedTryFinally_ = true;
}
jssrcnote* sn = GetSrcNote(gsn, script, pc);
MOZ_ASSERT(SN_TYPE(sn) == SRC_TRY);
jsbytecode* endpc =
pc + GetSrcNoteOffset(sn, SrcNote::Try::EndOfTryJumpOffset);
MOZ_ASSERT(JSOp(*endpc) == JSOP_GOTO);
MOZ_ASSERT(GetJumpOffset(endpc) > 0);
jsbytecode* afterTry = endpc + GetJumpOffset(endpc);
CFGBlock* tryBlock = CFGBlock::New(alloc(), GetNextPc(pc));
CFGBlock* successor = CFGBlock::New(alloc(), afterTry);
current->setStopIns(CFGTry::New(alloc(), tryBlock, endpc, successor));
current->setStopPc(pc);
if (!cfgStack_.append(CFGState::Try(endpc, successor))) {
return ControlStatus::Error;
}
current = tryBlock;
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processTryEnd(
CFGState& state) {
MOZ_ASSERT(state.state == CFGState::TRY);
MOZ_ASSERT(state.try_.successor);
if (current) {
current->setStopIns(CFGGoto::New(alloc(), state.try_.successor));
current->setStopPc(pc);
}
current = state.try_.successor;
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Joined;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processIfEnd(
CFGState& state) {
if (current) {
current->setStopIns(CFGGoto::New(alloc(), state.branch.ifFalse));
current->setStopPc(pc);
}
current = state.branch.ifFalse;
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Joined;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processIfElseTrueEnd(
CFGState& state) {
state.state = CFGState::IF_ELSE_FALSE;
state.branch.ifTrue = current;
state.stopAt = state.branch.falseEnd;
if (current) {
current->setStopPc(pc);
}
current = state.branch.ifFalse;
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processIfElseFalseEnd(
CFGState& state) {
state.branch.ifFalse = current;
if (current) {
current->setStopPc(pc);
}
CFGBlock* pred =
state.branch.ifTrue ? state.branch.ifTrue : state.branch.ifFalse;
CFGBlock* other = (pred == state.branch.ifTrue) ? state.branch.ifFalse
: state.branch.ifTrue;
if (!pred) {
return ControlStatus::Ended;
}
CFGBlock* join = CFGBlock::New(alloc(), state.branch.falseEnd);
pred->setStopIns(CFGGoto::New(alloc(), join));
if (other) {
other->setStopIns(CFGGoto::New(alloc(), join));
}
current = join;
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Joined;
}
CFGBlock* ControlFlowGenerator::createBreakCatchBlock(DeferredEdge* edge,
jsbytecode* pc) {
CFGBlock* successor = CFGBlock::New(alloc(), pc);
while (edge) {
if (!alloc().ensureBallast()) {
return nullptr;
}
CFGGoto* brk = CFGGoto::New(alloc(), successor);
edge->block->setStopIns(brk);
edge = edge->next;
}
return successor;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processDoWhileBodyEnd(
CFGState& state) {
if (!processDeferredContinues(state)) {
return ControlStatus::Error;
}
if (!current) {
return processBrokenLoop(state);
}
CFGBlock* header = CFGBlock::New(alloc(), state.loop.updatepc);
current->setStopIns(CFGGoto::New(alloc(), header));
current->setStopPc(pc);
state.state = CFGState::DO_WHILE_LOOP_COND;
state.stopAt = state.loop.updateEnd;
current = header;
pc = header->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processDoWhileCondEnd(
CFGState& state) {
MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE);
MOZ_ASSERT(current);
CFGBlock* successor = CFGBlock::New(alloc(), GetNextPc(pc));
CFGLoopEntry* entry = state.loop.entry->stopIns()->toLoopEntry();
entry->setLoopStopPc(pc);
CFGBlock* backEdge = CFGBlock::New(alloc(), entry->successor()->startPc());
backEdge->setStopIns(CFGBackEdge::New(alloc(), entry->successor()));
backEdge->setStopPc(entry->successor()->startPc());
if (!addBlock(backEdge)) {
return ControlStatus::Error;
}
CFGTest* test = CFGTest::New(alloc(), backEdge, successor);
current->setStopIns(test);
current->setStopPc(pc);
return finishLoop(state, successor);
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processWhileCondEnd(
CFGState& state) {
MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE || JSOp(*pc) == JSOP_IFEQ);
CFGBlock* body = CFGBlock::New(alloc(), state.loop.bodyStart);
state.loop.successor = CFGBlock::New(alloc(), state.loop.exitpc);
if (!body || !state.loop.successor) {
return ControlStatus::Error;
}
CFGTest* test;
if (JSOp(*pc) == JSOP_IFNE) {
test = CFGTest::New(alloc(), body, state.loop.successor);
} else {
test = CFGTest::New(alloc(), state.loop.successor, body);
}
current->setStopIns(test);
current->setStopPc(pc);
state.state = CFGState::WHILE_LOOP_BODY;
state.stopAt = state.loop.bodyEnd;
current = body;
pc = body->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processWhileBodyEnd(
CFGState& state) {
if (!processDeferredContinues(state)) {
return ControlStatus::Error;
}
if (!current) {
return processBrokenLoop(state);
}
CFGLoopEntry* entry = state.loop.entry->stopIns()->toLoopEntry();
entry->setLoopStopPc(pc);
current->setStopIns(CFGBackEdge::New(alloc(), entry->successor()));
if (pc != current->startPc()) {
current->setStopPc(pc);
} else {
current->setStartPc(entry->successor()->startPc());
current->setStopPc(entry->successor()->startPc());
}
return finishLoop(state, state.loop.successor);
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processForCondEnd(
CFGState& state) {
MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE);
CFGBlock* body = CFGBlock::New(alloc(), state.loop.bodyStart);
state.loop.successor = CFGBlock::New(alloc(), state.loop.exitpc);
CFGTest* test = CFGTest::New(alloc(), body, state.loop.successor);
current->setStopIns(test);
current->setStopPc(pc);
state.state = CFGState::FOR_LOOP_BODY;
state.stopAt = state.loop.bodyEnd;
current = body;
pc = body->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processForBodyEnd(
CFGState& state) {
if (!processDeferredContinues(state)) {
return ControlStatus::Error;
}
if (!state.loop.updatepc || !current) {
return processForUpdateEnd(state);
}
if (state.loop.updatepc != pc) {
CFGBlock* next = CFGBlock::New(alloc(), state.loop.updatepc);
current->setStopIns(CFGGoto::New(alloc(), next));
current->setStopPc(pc);
current = next;
if (!addBlock(current)) {
return ControlStatus::Error;
}
}
pc = state.loop.updatepc;
state.state = CFGState::FOR_LOOP_UPDATE;
state.stopAt = state.loop.updateEnd;
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processForUpdateEnd(
CFGState& state) {
if (!current) {
return processBrokenLoop(state);
}
CFGLoopEntry* entry = state.loop.entry->stopIns()->toLoopEntry();
entry->setLoopStopPc(pc);
current->setStopIns(CFGBackEdge::New(alloc(), entry->successor()));
if (pc != current->startPc()) {
current->setStopPc(pc);
} else {
current->setStartPc(entry->successor()->startPc());
current->setStopPc(entry->successor()->startPc());
}
return finishLoop(state, state.loop.successor);
}
ControlFlowGenerator::ControlStatus
ControlFlowGenerator::processWhileOrForInOrForOfLoop(jssrcnote* sn) {
MOZ_ASSERT(SN_TYPE(sn) == SRC_FOR_OF || SN_TYPE(sn) == SRC_FOR_IN ||
SN_TYPE(sn) == SRC_WHILE);
static_assert(unsigned(SrcNote::While::BackJumpOffset) ==
unsigned(SrcNote::ForIn::BackJumpOffset),
"SrcNote::{While,ForIn,ForOf}::BackJumpOffset should be same");
static_assert(unsigned(SrcNote::While::BackJumpOffset) ==
unsigned(SrcNote::ForOf::BackJumpOffset),
"SrcNote::{While,ForIn,ForOf}::BackJumpOffset should be same");
int backjumppcOffset = GetSrcNoteOffset(sn, SrcNote::While::BackJumpOffset);
jsbytecode* backjumppc = pc + backjumppcOffset;
MOZ_ASSERT(backjumppc > pc);
MOZ_ASSERT(JSOp(*GetNextPc(pc)) == JSOP_LOOPHEAD);
MOZ_ASSERT(GetNextPc(pc) == backjumppc + GetJumpOffset(backjumppc));
jsbytecode* loopEntry = pc + GetJumpOffset(pc);
size_t stackPhiCount;
if (SN_TYPE(sn) == SRC_FOR_OF) {
stackPhiCount = 3;
} else if (SN_TYPE(sn) == SRC_FOR_IN) {
stackPhiCount = 1;
} else {
stackPhiCount = 0;
}
jsbytecode* loopHead = GetNextPc(pc);
jsbytecode* bodyStart = GetNextPc(loopHead);
jsbytecode* bodyEnd = pc + GetJumpOffset(pc);
jsbytecode* exitpc = GetNextPc(backjumppc);
jsbytecode* continuepc = pc;
CFGBlock* header = CFGBlock::New(alloc(), loopEntry);
CFGLoopEntry* ins = CFGLoopEntry::New(alloc(), header, stackPhiCount);
if (LoopEntryCanIonOsr(loopEntry)) {
ins->setCanOsr();
}
if (SN_TYPE(sn) == SRC_FOR_IN) {
ins->setIsForIn();
}
current->setStopIns(ins);
current->setStopPc(pc);
if (!pushLoop(CFGState::WHILE_LOOP_COND, backjumppc, current, loopHead,
bodyEnd, bodyStart, bodyEnd, exitpc, continuepc)) {
return ControlStatus::Error;
}
current = header;
pc = header->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processBrokenLoop(
CFGState& state) {
MOZ_ASSERT(!current);
state.loop.entry->stopIns()->toLoopEntry()->setIsBrokenLoop();
current = state.loop.successor;
if (current) {
if (!addBlock(current)) {
return ControlStatus::Error;
}
}
if (state.loop.breaks) {
CFGBlock* block =
createBreakCatchBlock(state.loop.breaks, state.loop.exitpc);
if (!block) {
return ControlStatus::Error;
}
if (current) {
current->setStopIns(CFGGoto::New(alloc(), block));
current->setStopPc(current->startPc());
}
current = block;
if (!addBlock(current)) {
return ControlStatus::Error;
}
}
if (!current) {
return ControlStatus::Ended;
}
pc = current->startPc();
return ControlStatus::Joined;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::finishLoop(
CFGState& state, CFGBlock* successor) {
MOZ_ASSERT(current);
if (state.loop.breaks) {
if (successor) {
if (!addBlock(successor)) {
return ControlStatus::Error;
}
}
CFGBlock* block =
createBreakCatchBlock(state.loop.breaks, state.loop.exitpc);
if (!block) {
return ControlStatus::Error;
}
if (successor) {
successor->setStopIns(CFGGoto::New(alloc(), block));
successor->setStopPc(successor->startPc());
}
successor = block;
}
if (!successor) {
current = nullptr;
return ControlStatus::Ended;
}
current = successor;
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Joined;
}
bool ControlFlowGenerator::processDeferredContinues(CFGState& state) {
if (state.loop.continues) {
DeferredEdge* edge = state.loop.continues;
CFGBlock* update = CFGBlock::New(alloc(), pc);
if (current) {
current->setStopIns(CFGGoto::New(alloc(), update));
current->setStopPc(pc);
}
while (edge) {
if (!alloc().ensureBallast()) {
return false;
}
edge->block->setStopIns(CFGGoto::New(alloc(), update));
edge = edge->next;
}
state.loop.continues = nullptr;
current = update;
if (!addBlock(current)) {
return false;
}
}
return true;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processCondSwitch() {
MOZ_ASSERT(JSOp(*pc) == JSOP_CONDSWITCH);
jssrcnote* sn = GetSrcNote(gsn, script, pc);
MOZ_ASSERT(SN_TYPE(sn) == SRC_CONDSWITCH);
jsbytecode* exitpc =
pc + GetSrcNoteOffset(sn, SrcNote::CondSwitch::EndOffset);
jsbytecode* firstCase =
pc + GetSrcNoteOffset(sn, SrcNote::CondSwitch::FirstCaseOffset);
jsbytecode* curCase = firstCase;
jsbytecode* lastTarget = GetJumpOffset(curCase) + curCase;
size_t nbBodies = 1;
MOZ_ASSERT(pc < curCase && curCase <= exitpc);
while (JSOp(*curCase) == JSOP_CASE) {
jssrcnote* caseSn = GetSrcNote(gsn, script, curCase);
MOZ_ASSERT(caseSn && SN_TYPE(caseSn) == SRC_NEXTCASE);
ptrdiff_t off = GetSrcNoteOffset(caseSn, SrcNote::NextCase::NextCaseOffset);
MOZ_ASSERT_IF(off == 0, JSOp(*GetNextPc(curCase)) == JSOP_JUMPTARGET);
curCase = off ? curCase + off : GetNextPc(GetNextPc(curCase));
MOZ_ASSERT(pc < curCase && curCase <= exitpc);
jsbytecode* curTarget = GetJumpOffset(curCase) + curCase;
if (lastTarget < curTarget) {
nbBodies++;
}
lastTarget = curTarget;
}
MOZ_ASSERT(JSOp(*curCase) == JSOP_DEFAULT);
jsbytecode* defaultTarget = GetJumpOffset(curCase) + curCase;
MOZ_ASSERT(curCase < defaultTarget && defaultTarget <= exitpc);
curCase = firstCase;
lastTarget = nullptr;
size_t defaultIdx = 0;
while (JSOp(*curCase) == JSOP_CASE) {
jsbytecode* curTarget = GetJumpOffset(curCase) + curCase;
if (lastTarget < defaultTarget && defaultTarget <= curTarget) {
if (defaultTarget < curTarget) {
nbBodies++;
}
break;
}
if (lastTarget < curTarget) {
defaultIdx++;
}
jssrcnote* caseSn = GetSrcNote(gsn, script, curCase);
ptrdiff_t off = GetSrcNoteOffset(caseSn, SrcNote::NextCase::NextCaseOffset);
curCase = off ? curCase + off : GetNextPc(GetNextPc(curCase));
lastTarget = curTarget;
}
CFGState state = CFGState::CondSwitch(alloc(), exitpc, defaultTarget);
if (!state.switch_.bodies || !state.switch_.bodies->init(alloc(), nbBodies)) {
return ControlStatus::Error;
}
state.switch_.defaultIdx = defaultIdx;
FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
bodies[state.switch_.defaultIdx] = CFGBlock::New(alloc(), defaultTarget);
if (state.switch_.defaultIdx == 0) {
state.switch_.currentIdx++;
}
MOZ_ASSERT(JSOp(*firstCase) == JSOP_CASE);
state.stopAt = firstCase;
state.state = CFGState::COND_SWITCH_CASE;
if (!cfgStack_.append(state)) {
return ControlStatus::Error;
}
jsbytecode* nextPc = GetNextPc(pc);
CFGBlock* next = CFGBlock::New(alloc(), nextPc);
current->setStopIns(CFGGoto::New(alloc(), next));
current->setStopPc(pc);
current = next;
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processCondSwitchCase(
CFGState& state) {
MOZ_ASSERT(state.state == CFGState::COND_SWITCH_CASE);
MOZ_ASSERT(!state.switch_.breaks);
MOZ_ASSERT(current);
MOZ_ASSERT(JSOp(*pc) == JSOP_CASE);
FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
uint32_t& currentIdx = state.switch_.currentIdx;
jsbytecode* lastTarget =
currentIdx ? bodies[currentIdx - 1]->startPc() : nullptr;
jssrcnote* sn = GetSrcNote(gsn, script, pc);
ptrdiff_t off = GetSrcNoteOffset(sn, SrcNote::NextCase::NextCaseOffset);
MOZ_ASSERT_IF(off == 0, JSOp(*GetNextPc(pc)) == JSOP_JUMPTARGET);
jsbytecode* casePc = off ? pc + off : GetNextPc(GetNextPc(pc));
bool nextIsDefault = JSOp(*casePc) == JSOP_DEFAULT;
MOZ_ASSERT(JSOp(*casePc) == JSOP_CASE || nextIsDefault);
jsbytecode* bodyTarget = pc + GetJumpOffset(pc);
CFGBlock* bodyBlock = nullptr;
if (lastTarget < bodyTarget) {
if (currentIdx == state.switch_.defaultIdx) {
currentIdx++;
lastTarget = bodies[currentIdx - 1]->startPc();
if (lastTarget < bodyTarget) {
bodyBlock = CFGBlock::New(alloc(), bodyTarget);
bodies[currentIdx++] = bodyBlock;
} else {
MOZ_ASSERT(lastTarget == bodyTarget);
MOZ_ASSERT(currentIdx > 0);
bodyBlock = bodies[currentIdx - 1];
}
} else {
bodyBlock = CFGBlock::New(alloc(), bodyTarget);
bodies[currentIdx++] = bodyBlock;
}
} else {
MOZ_ASSERT(lastTarget == bodyTarget);
MOZ_ASSERT(currentIdx > 0);
bodyBlock = bodies[currentIdx - 1];
}
CFGBlock* emptyBlock = CFGBlock::New(alloc(), bodyBlock->startPc());
emptyBlock->setStopIns(CFGGoto::New(alloc(), bodyBlock));
emptyBlock->setStopPc(bodyBlock->startPc());
if (!addBlock(emptyBlock)) {
return ControlStatus::Error;
}
if (nextIsDefault) {
CFGBlock* defaultBlock = bodies[state.switch_.defaultIdx];
CFGBlock* emptyBlock2 = CFGBlock::New(alloc(), defaultBlock->startPc());
emptyBlock2->setStopIns(CFGGoto::New(alloc(), defaultBlock));
emptyBlock2->setStopPc(defaultBlock->startPc());
if (!addBlock(emptyBlock2)) {
return ControlStatus::Error;
}
current->setStopIns(CFGCondSwitchCase::NewFalseBranchIsDefault(
alloc(), emptyBlock, emptyBlock2));
current->setStopPc(pc);
return processCondSwitchDefault(state);
}
CFGBlock* nextBlock = CFGBlock::New(alloc(), GetNextPc(pc));
current->setStopIns(CFGCondSwitchCase::NewFalseBranchIsNextCase(
alloc(), emptyBlock, nextBlock));
current->setStopPc(pc);
current = nextBlock;
pc = current->startPc();
state.stopAt = casePc;
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus
ControlFlowGenerator::processCondSwitchDefault(CFGState& state) {
uint32_t& currentIdx = state.switch_.currentIdx;
#ifdef DEBUG
FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
MOZ_ASSERT(state.switch_.currentIdx == bodies.length() ||
state.switch_.defaultIdx + 1 == bodies.length());
#endif
ControlFlowInfo breakInfo(cfgStack_.length() - 1, state.switch_.exitpc);
if (!switches_.append(breakInfo)) {
return ControlStatus::Error;
}
currentIdx = 0;
current = nullptr;
state.state = CFGState::COND_SWITCH_BODY;
return processCondSwitchBody(state);
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processCondSwitchBody(
CFGState& state) {
MOZ_ASSERT(state.state == CFGState::COND_SWITCH_BODY);
MOZ_ASSERT(pc <= state.switch_.exitpc);
FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
uint32_t& currentIdx = state.switch_.currentIdx;
MOZ_ASSERT(currentIdx <= bodies.length());
if (currentIdx == bodies.length()) {
MOZ_ASSERT_IF(current, pc == state.switch_.exitpc);
return processSwitchEnd(state.switch_.breaks, state.switch_.exitpc);
}
CFGBlock* nextBody = bodies[currentIdx++];
MOZ_ASSERT_IF(current, pc == nextBody->startPc());
if (current) {
current->setStopIns(CFGGoto::New(alloc(), nextBody));
current->setStopPc(pc);
}
current = nextBody;
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
if (currentIdx < bodies.length()) {
state.stopAt = bodies[currentIdx]->startPc();
} else {
state.stopAt = state.switch_.exitpc;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processAndOrEnd(
CFGState& state) {
MOZ_ASSERT(current);
CFGBlock* lhs = state.branch.ifFalse;
CFGBlock* join = CFGBlock::New(alloc(), state.stopAt);
current->setStopIns(CFGGoto::New(alloc(), join));
current->setStopPc(pc);
lhs->setStopIns(CFGGoto::New(alloc(), join));
lhs->setStopPc(pc);
current = join;
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Joined;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processForLoop(
JSOp op, jssrcnote* sn) {
MOZ_ASSERT(op == JSOP_NOP);
pc = GetNextPc(pc);
jsbytecode* condpc = pc + GetSrcNoteOffset(sn, SrcNote::For::CondOffset);
jsbytecode* updatepc = pc + GetSrcNoteOffset(sn, SrcNote::For::UpdateOffset);
jsbytecode* backjumppc =
pc + GetSrcNoteOffset(sn, SrcNote::For::BackJumpOffset);
jsbytecode* exitpc = GetNextPc(backjumppc);
jsbytecode* bodyStart = pc;
jsbytecode* bodyEnd = updatepc;
jsbytecode* loopEntry = condpc;
if (condpc != backjumppc) {
MOZ_ASSERT(JSOp(*bodyStart) == JSOP_GOTO);
MOZ_ASSERT(bodyStart + GetJumpOffset(bodyStart) == condpc);
bodyStart = GetNextPc(bodyStart);
} else {
if (op != JSOP_NOP) {
MOZ_ASSERT(JSOp(*bodyStart) == JSOP_NOP);
bodyStart = GetNextPc(bodyStart);
}
loopEntry = GetNextPc(bodyStart);
}
jsbytecode* loopHead = bodyStart;
MOZ_ASSERT(JSOp(*bodyStart) == JSOP_LOOPHEAD);
MOZ_ASSERT(backjumppc + GetJumpOffset(backjumppc) == bodyStart);
bodyStart = GetNextPc(bodyStart);
MOZ_ASSERT(JSOp(*loopEntry) == JSOP_LOOPENTRY);
CFGBlock* header = CFGBlock::New(alloc(), loopEntry);
CFGLoopEntry* ins = CFGLoopEntry::New(alloc(), header, 0);
if (LoopEntryCanIonOsr(loopEntry)) {
ins->setCanOsr();
}
current->setStopIns(ins);
current->setStopPc(pc);
jsbytecode* stopAt;
CFGState::State initial;
if (condpc != backjumppc) {
pc = condpc;
stopAt = backjumppc;
initial = CFGState::FOR_LOOP_COND;
} else {
pc = bodyStart;
stopAt = bodyEnd;
initial = CFGState::FOR_LOOP_BODY;
}
if (!pushLoop(initial, stopAt, current, loopHead, pc, bodyStart, bodyEnd,
exitpc, updatepc)) {
return ControlStatus::Error;
}
CFGState& state = cfgStack_.back();
state.loop.condpc = (condpc != backjumppc) ? condpc : nullptr;
state.loop.updatepc = (updatepc != condpc) ? updatepc : nullptr;
if (state.loop.updatepc) {
state.loop.updateEnd = condpc;
}
current = header;
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processDoWhileLoop(
jssrcnote* sn) {
int condition_offset = GetSrcNoteOffset(sn, SrcNote::DoWhile::CondOffset);
jsbytecode* conditionpc = pc + condition_offset;
int offset = GetSrcNoteOffset(sn, SrcNote::DoWhile::BackJumpOffset);
jsbytecode* ifne = pc + offset;
MOZ_ASSERT(ifne > pc);
jsbytecode* loopHead = pc;
MOZ_ASSERT(JSOp(*loopHead) == JSOP_LOOPHEAD);
MOZ_ASSERT(loopHead == ifne + GetJumpOffset(ifne));
jsbytecode* loopEntry = GetNextPc(loopHead);
CFGBlock* header = CFGBlock::New(alloc(), loopEntry);
CFGLoopEntry* ins = CFGLoopEntry::New(alloc(), header, 0);
if (LoopEntryCanIonOsr(loopEntry)) {
ins->setCanOsr();
}
current->setStopIns(ins);
current->setStopPc(pc);
jsbytecode* bodyEnd = conditionpc;
jsbytecode* exitpc = GetNextPc(ifne);
if (!pushLoop(CFGState::DO_WHILE_LOOP_BODY, conditionpc, current, loopHead,
loopEntry, loopEntry, bodyEnd, exitpc, conditionpc)) {
return ControlStatus::Error;
}
CFGState& state = cfgStack_.back();
state.loop.updatepc = conditionpc;
state.loop.updateEnd = ifne;
current = header;
pc = loopEntry;
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
bool ControlFlowGenerator::pushLoop(CFGState::State initial, jsbytecode* stopAt,
CFGBlock* entry, jsbytecode* loopHead,
jsbytecode* initialPc,
jsbytecode* bodyStart, jsbytecode* bodyEnd,
jsbytecode* exitpc,
jsbytecode* continuepc) {
ControlFlowInfo loop(cfgStack_.length(), continuepc);
if (!loops_.append(loop)) {
return false;
}
CFGState state;
state.state = initial;
state.stopAt = stopAt;
state.loop.bodyStart = bodyStart;
state.loop.bodyEnd = bodyEnd;
state.loop.exitpc = exitpc;
state.loop.entry = entry;
state.loop.successor = nullptr;
state.loop.breaks = nullptr;
state.loop.continues = nullptr;
state.loop.initialState = initial;
state.loop.initialPc = initialPc;
state.loop.initialStopAt = stopAt;
state.loop.loopHead = loopHead;
return cfgStack_.append(state);
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processBreak(
JSOp op, jssrcnote* sn) {
MOZ_ASSERT(op == JSOP_GOTO);
MOZ_ASSERT(SN_TYPE(sn) == SRC_BREAK || SN_TYPE(sn) == SRC_BREAK2LABEL);
jsbytecode* target = pc + GetJumpOffset(pc);
DebugOnly<bool> found = false;
if (SN_TYPE(sn) == SRC_BREAK2LABEL) {
for (size_t i = labels_.length() - 1;; i--) {
CFGState& cfg = cfgStack_[labels_[i].cfgEntry];
MOZ_ASSERT(cfg.state == CFGState::LABEL);
if (cfg.stopAt == target) {
cfg.label.breaks =
new (alloc()) DeferredEdge(current, cfg.label.breaks);
found = true;
break;
}
if (i == 0) {
break;
}
}
} else {
for (size_t i = loops_.length() - 1;; i--) {
CFGState& cfg = cfgStack_[loops_[i].cfgEntry];
MOZ_ASSERT(cfg.isLoop());
if (cfg.loop.exitpc == target) {
cfg.loop.breaks = new (alloc()) DeferredEdge(current, cfg.loop.breaks);
found = true;
break;
}
if (i == 0) {
break;
}
}
}
current->setStopPc(pc);
MOZ_ASSERT(found);
current = nullptr;
pc += CodeSpec[op].length;
return processControlEnd();
}
static inline jsbytecode* EffectiveContinue(jsbytecode* pc) {
if (JSOp(*pc) == JSOP_GOTO) {
return pc + GetJumpOffset(pc);
}
return pc;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processContinue(
JSOp op) {
MOZ_ASSERT(op == JSOP_GOTO);
CFGState* found = nullptr;
jsbytecode* target = pc + GetJumpOffset(pc);
for (size_t i = loops_.length() - 1;; i--) {
if (loops_[i].continuepc == target + JSOP_JUMPTARGET_LENGTH ||
EffectiveContinue(loops_[i].continuepc) == target) {
found = &cfgStack_[loops_[i].cfgEntry];
break;
}
if (i == 0) {
break;
}
}
MOZ_ASSERT(found);
CFGState& state = *found;
state.loop.continues =
new (alloc()) DeferredEdge(current, state.loop.continues);
if (!state.loop.continues) {
return ControlStatus::Error;
}
current->setStopPc(pc);
current = nullptr;
pc += CodeSpec[op].length;
return processControlEnd();
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processSwitchBreak(
JSOp op) {
MOZ_ASSERT(op == JSOP_GOTO);
CFGState* found = nullptr;
jsbytecode* target = pc + GetJumpOffset(pc);
for (size_t i = switches_.length() - 1;; i--) {
if (switches_[i].continuepc == target) {
found = &cfgStack_[switches_[i].cfgEntry];
break;
}
if (i == 0) {
break;
}
}
MOZ_ASSERT(found);
CFGState& state = *found;
DeferredEdge** breaks = nullptr;
switch (state.state) {
case CFGState::TABLE_SWITCH:
breaks = &state.switch_.breaks;
break;
case CFGState::COND_SWITCH_BODY:
breaks = &state.switch_.breaks;
break;
default:
MOZ_CRASH("Unexpected switch state.");
}
*breaks = new (alloc()) DeferredEdge(current, *breaks);
current->setStopPc(pc);
current = nullptr;
pc += CodeSpec[op].length;
return processControlEnd();
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processIfStart(
JSOp op) {
jsbytecode* trueStart = pc + CodeSpec[op].length;
jsbytecode* falseStart = pc + GetJumpOffset(pc);
MOZ_ASSERT(falseStart > pc);
jssrcnote* sn = GetSrcNote(gsn, script, pc);
if (!sn) {
return ControlStatus::Error;
}
CFGBlock* ifTrue = CFGBlock::New(alloc(), trueStart);
CFGBlock* ifFalse = CFGBlock::New(alloc(), falseStart);
CFGTest* test = CFGTest::New(alloc(), ifTrue, ifFalse);
current->setStopIns(test);
current->setStopPc(pc);
switch (SN_TYPE(sn)) {
case SRC_IF:
if (!cfgStack_.append(CFGState::If(falseStart, test))) {
return ControlStatus::Error;
}
break;
case SRC_IF_ELSE:
case SRC_COND: {
MOZ_ASSERT(JSOp(*falseStart) == JSOP_JUMPTARGET);
jsbytecode* trueEnd = falseStart - JSOP_GOTO_LENGTH;
MOZ_ASSERT(trueEnd > pc);
MOZ_ASSERT(JSOp(*trueEnd) == JSOP_GOTO);
MOZ_ASSERT(!GetSrcNote(gsn, script, trueEnd));
jsbytecode* falseEnd = trueEnd + GetJumpOffset(trueEnd);
MOZ_ASSERT(falseEnd > trueEnd);
MOZ_ASSERT(falseEnd >= falseStart);
if (!cfgStack_.append(CFGState::IfElse(trueEnd, falseEnd, test))) {
return ControlStatus::Error;
}
break;
}
default:
MOZ_CRASH("unexpected source note type");
}
current = ifTrue;
pc = ifTrue->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
int ControlFlowGenerator::CmpSuccessors(const void* a, const void* b) {
const CFGBlock* a0 = *(CFGBlock* const*)a;
const CFGBlock* b0 = *(CFGBlock* const*)b;
if (a0->startPc() == b0->startPc()) {
return 0;
}
return (a0->startPc() > b0->startPc()) ? 1 : -1;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processTableSwitch(
JSOp op, jssrcnote* sn) {
MOZ_ASSERT(op == JSOP_TABLESWITCH);
MOZ_ASSERT(SN_TYPE(sn) == SRC_TABLESWITCH);
jsbytecode* exitpc =
pc + GetSrcNoteOffset(sn, SrcNote::TableSwitch::EndOffset);
jsbytecode* defaultpc = pc + GET_JUMP_OFFSET(pc);
MOZ_ASSERT(defaultpc > pc && defaultpc <= exitpc);
jsbytecode* pc2 = pc;
pc2 += JUMP_OFFSET_LEN;
int low = GET_JUMP_OFFSET(pc2);
pc2 += JUMP_OFFSET_LEN;
int high = GET_JUMP_OFFSET(pc2);
pc2 += JUMP_OFFSET_LEN;
CFGTableSwitch* tableswitch = CFGTableSwitch::New(alloc(), low, high);
CFGBlock* defaultcase = CFGBlock::New(alloc(), defaultpc);
if (!tableswitch->addDefault(defaultcase)) {
return ControlStatus::Error;
}
for (int i = 0; i < high - low + 1; i++) {
if (!alloc().ensureBallast()) {
return ControlStatus::Error;
}
jsbytecode* casepc = script->tableSwitchCasePC(pc, i);
MOZ_ASSERT(casepc >= pc && casepc <= exitpc);
CFGBlock* caseBlock;
if (casepc == defaultpc) {
caseBlock = CFGBlock::New(alloc(), defaultpc);
caseBlock->setStopIns(CFGGoto::New(alloc(), defaultcase));
} else {
caseBlock = CFGBlock::New(alloc(), casepc);
}
if (!tableswitch->addCase(caseBlock)) {
return ControlStatus::Error;
}
pc2 += JUMP_OFFSET_LEN;
}
ControlFlowInfo switchinfo(cfgStack_.length(), exitpc);
if (!switches_.append(switchinfo)) {
return ControlStatus::Error;
}
CFGState state = CFGState::TableSwitch(alloc(), exitpc);
if (!state.switch_.bodies ||
!state.switch_.bodies->init(alloc(), tableswitch->numSuccessors())) {
return ControlStatus::Error;
}
FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
for (size_t i = 0; i < tableswitch->numSuccessors(); i++) {
bodies[i] = tableswitch->getSuccessor(i);
}
qsort(bodies.begin(), state.switch_.bodies->length(), sizeof(CFGBlock*),
CmpSuccessors);
current->setStopIns(tableswitch);
current->setStopPc(pc);
if (bodies.length() > 1) {
state.stopAt = bodies[1]->startPc();
} else {
state.stopAt = exitpc;
}
if (!cfgStack_.append(state)) {
return ControlStatus::Error;
}
current = bodies[0];
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus
ControlFlowGenerator::processNextTableSwitchCase(CFGState& state) {
MOZ_ASSERT(state.state == CFGState::TABLE_SWITCH);
FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
state.switch_.currentIdx++;
if (state.switch_.currentIdx >= bodies.length()) {
return processSwitchEnd(state.switch_.breaks, state.switch_.exitpc);
}
CFGBlock* successor = bodies[state.switch_.currentIdx];
if (current) {
current->setStopIns(CFGGoto::New(alloc(), successor));
current->setStopPc(pc);
}
if (state.switch_.currentIdx + 1 < bodies.length()) {
state.stopAt = bodies[state.switch_.currentIdx + 1]->startPc();
} else {
state.stopAt = state.switch_.exitpc;
}
current = bodies[state.switch_.currentIdx];
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processSwitchEnd(
DeferredEdge* breaks, jsbytecode* exitpc) {
if (!breaks && !current) {
return ControlStatus::Ended;
}
CFGBlock* successor = nullptr;
if (breaks) {
successor = createBreakCatchBlock(breaks, exitpc);
if (!successor) {
return ControlStatus::Error;
}
} else {
successor = CFGBlock::New(alloc(), exitpc);
}
if (current) {
current->setStopIns(CFGGoto::New(alloc(), successor));
current->setStopPc(pc);
}
current = successor;
pc = successor->startPc();
if (!addBlock(successor)) {
return ControlStatus::Error;
}
return ControlStatus::Joined;
}
ControlFlowGenerator::CFGState ControlFlowGenerator::CFGState::If(
jsbytecode* join, CFGTest* test) {
CFGState state;
state.state = IF_TRUE;
state.stopAt = join;
state.branch.ifFalse = test->getSuccessor(1);
state.branch.test = test;
return state;
}
ControlFlowGenerator::CFGState ControlFlowGenerator::CFGState::IfElse(
jsbytecode* trueEnd, jsbytecode* falseEnd, CFGTest* test) {
CFGBlock* ifFalse = test->getSuccessor(1);
CFGState state;
state.state =
(falseEnd == ifFalse->startPc()) ? IF_TRUE_EMPTY_ELSE : IF_ELSE_TRUE;
state.stopAt = trueEnd;
state.branch.falseEnd = falseEnd;
state.branch.ifFalse = ifFalse;
state.branch.test = test;
return state;
}
ControlFlowGenerator::CFGState ControlFlowGenerator::CFGState::AndOr(
jsbytecode* join, CFGBlock* lhs) {
CFGState state;
state.state = AND_OR;
state.stopAt = join;
state.branch.ifFalse = lhs;
state.branch.test = nullptr;
return state;
}
ControlFlowGenerator::CFGState ControlFlowGenerator::CFGState::TableSwitch(
TempAllocator& alloc, jsbytecode* exitpc) {
CFGState state;
state.state = TABLE_SWITCH;
state.stopAt = exitpc;
state.switch_.bodies =
(FixedList<CFGBlock*>*)alloc.allocate(sizeof(FixedList<CFGBlock*>));
state.switch_.currentIdx = 0;
state.switch_.exitpc = exitpc;
state.switch_.breaks = nullptr;
return state;
}
ControlFlowGenerator::CFGState ControlFlowGenerator::CFGState::CondSwitch(
TempAllocator& alloc, jsbytecode* exitpc, jsbytecode* defaultTarget) {
CFGState state;
state.state = COND_SWITCH_CASE;
state.stopAt = nullptr;
state.switch_.bodies =
(FixedList<CFGBlock*>*)alloc.allocate(sizeof(FixedList<CFGBlock*>));
state.switch_.currentIdx = 0;
state.switch_.defaultTarget = defaultTarget;
state.switch_.defaultIdx = uint32_t(-1);
state.switch_.exitpc = exitpc;
state.switch_.breaks = nullptr;
return state;
}
ControlFlowGenerator::CFGState ControlFlowGenerator::CFGState::Label(
jsbytecode* exitpc) {
CFGState state;
state.state = LABEL;
state.stopAt = exitpc;
state.label.breaks = nullptr;
return state;
}
ControlFlowGenerator::CFGState ControlFlowGenerator::CFGState::Try(
jsbytecode* exitpc, CFGBlock* successor) {
CFGState state;
state.state = TRY;
state.stopAt = exitpc;
state.try_.successor = successor;
return state;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processAndOr(
JSOp op) {
MOZ_ASSERT(op == JSOP_AND || op == JSOP_OR);
jsbytecode* rhsStart = pc + CodeSpec[op].length;
jsbytecode* joinStart = pc + GetJumpOffset(pc);
MOZ_ASSERT(joinStart > pc);
CFGBlock* evalLhs = CFGBlock::New(alloc(), joinStart);
CFGBlock* evalRhs = CFGBlock::New(alloc(), rhsStart);
CFGTest* test = (op == JSOP_AND) ? CFGTest::New(alloc(), evalRhs, evalLhs)
: CFGTest::New(alloc(), evalLhs, evalRhs);
test->keepCondition();
current->setStopIns(test);
current->setStopPc(pc);
if (!cfgStack_.append(CFGState::AndOr(joinStart, evalLhs))) {
return ControlStatus::Error;
}
if (!addBlock(evalLhs)) {
return ControlStatus::Error;
}
current = evalRhs;
pc = current->startPc();
if (!addBlock(current)) {
return ControlStatus::Error;
}
return ControlStatus::Jumped;
}
ControlFlowGenerator::ControlStatus ControlFlowGenerator::processLabel() {
MOZ_ASSERT(JSOp(*pc) == JSOP_LABEL);
jsbytecode* endpc = pc + GET_CODE_OFFSET(pc);
MOZ_ASSERT(endpc > pc);
ControlFlowInfo label(cfgStack_.length(), endpc);
if (!labels_.append(label)) {
return ControlStatus::Error;
}
if (!cfgStack_.append(CFGState::Label(endpc))) {
return ControlStatus::Error;
}
return ControlStatus::None;
}