/* -*- 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/BytecodeAnalysis.h" #include "jsopcode.h" #include "jit/JitSpewer.h" #include "jsopcodeinlines.h" #include "jsscriptinlines.h" using namespace js; using namespace js::jit; BytecodeAnalysis::BytecodeAnalysis(TempAllocator& alloc, JSScript* script) : script_(script), infos_(alloc), usesEnvironmentChain_(false), hasTryFinally_(false), hasSetArg_(false) { } // Bytecode range containing only catch or finally code. struct CatchFinallyRange { uint32_t start; // Inclusive. uint32_t end; // Exclusive. CatchFinallyRange(uint32_t start, uint32_t end) : start(start), end(end) { MOZ_ASSERT(end > start); } bool contains(uint32_t offset) const { return start <= offset && offset < end; } }; bool BytecodeAnalysis::init(TempAllocator& alloc, GSNCache& gsn) { if (!infos_.growByUninitialized(script_->length())) return false; // Initialize the env chain slot if either the function needs some // EnvironmentObject (like a CallObject) or the script uses the env // chain. The latter case is handled below. usesEnvironmentChain_ = script_->module() || script_->initialEnvironmentShape() || (script_->functionDelazifying() && script_->functionDelazifying()->needsSomeEnvironmentObject()); jsbytecode* end = script_->codeEnd(); // Clear all BytecodeInfo. mozilla::PodZero(infos_.begin(), infos_.length()); infos_[0].init(/*stackDepth=*/0); Vector catchFinallyRanges(alloc); jsbytecode* nextpc; for (jsbytecode* pc = script_->code(); pc < end; pc = nextpc) { JSOp op = JSOp(*pc); nextpc = pc + GetBytecodeLength(pc); unsigned offset = script_->pcToOffset(pc); JitSpew(JitSpew_BaselineOp, "Analyzing op @ %d (end=%d): %s", int(script_->pcToOffset(pc)), int(script_->length()), CodeName[op]); // If this bytecode info has not yet been initialized, it's not reachable. if (!infos_[offset].initialized) continue; unsigned stackDepth = infos_[offset].stackDepth; #ifdef DEBUG for (jsbytecode* chkpc = pc + 1; chkpc < (pc + GetBytecodeLength(pc)); chkpc++) MOZ_ASSERT(!infos_[script_->pcToOffset(chkpc)].initialized); #endif unsigned nuses = GetUseCount(script_, offset); unsigned ndefs = GetDefCount(script_, offset); MOZ_ASSERT(stackDepth >= nuses); stackDepth -= nuses; stackDepth += ndefs; // If stack depth exceeds max allowed by analysis, fail fast. MOZ_ASSERT(stackDepth <= BytecodeInfo::MAX_STACK_DEPTH); switch (op) { case JSOP_TABLESWITCH: { unsigned defaultOffset = offset + GET_JUMP_OFFSET(pc); jsbytecode* pc2 = pc + JUMP_OFFSET_LEN; int32_t low = GET_JUMP_OFFSET(pc2); pc2 += JUMP_OFFSET_LEN; int32_t high = GET_JUMP_OFFSET(pc2); pc2 += JUMP_OFFSET_LEN; infos_[defaultOffset].init(stackDepth); infos_[defaultOffset].jumpTarget = true; for (int32_t i = low; i <= high; i++) { unsigned targetOffset = offset + GET_JUMP_OFFSET(pc2); if (targetOffset != offset) { infos_[targetOffset].init(stackDepth); infos_[targetOffset].jumpTarget = true; } pc2 += JUMP_OFFSET_LEN; } break; } case JSOP_TRY: { JSTryNote* tn = script_->trynotes()->vector; JSTryNote* tnlimit = tn + script_->trynotes()->length; for (; tn < tnlimit; tn++) { unsigned startOffset = script_->mainOffset() + tn->start; if (startOffset == offset + 1) { unsigned catchOffset = startOffset + tn->length; if (tn->kind != JSTRY_FOR_IN) { infos_[catchOffset].init(stackDepth); infos_[catchOffset].jumpTarget = true; } } } // Get the pc of the last instruction in the try block. It's a JSOP_GOTO to // jump over the catch/finally blocks. jssrcnote* sn = GetSrcNote(gsn, script_, pc); MOZ_ASSERT(SN_TYPE(sn) == SRC_TRY); jsbytecode* endOfTry = pc + GetSrcNoteOffset(sn, 0); MOZ_ASSERT(JSOp(*endOfTry) == JSOP_GOTO); jsbytecode* afterTry = endOfTry + GET_JUMP_OFFSET(endOfTry); MOZ_ASSERT(afterTry > endOfTry); // Pop CatchFinallyRanges that are no longer needed. while (!catchFinallyRanges.empty() && catchFinallyRanges.back().end <= offset) catchFinallyRanges.popBack(); CatchFinallyRange range(script_->pcToOffset(endOfTry), script_->pcToOffset(afterTry)); if (!catchFinallyRanges.append(range)) return false; break; } case JSOP_LOOPENTRY: for (size_t i = 0; i < catchFinallyRanges.length(); i++) { if (catchFinallyRanges[i].contains(offset)) infos_[offset].loopEntryInCatchOrFinally = true; } break; case JSOP_GETNAME: case JSOP_BINDNAME: case JSOP_BINDVAR: case JSOP_SETNAME: case JSOP_STRICTSETNAME: case JSOP_DELNAME: case JSOP_GETALIASEDVAR: case JSOP_SETALIASEDVAR: case JSOP_LAMBDA: case JSOP_LAMBDA_ARROW: case JSOP_DEFFUN: case JSOP_DEFVAR: usesEnvironmentChain_ = true; break; case JSOP_GETGNAME: case JSOP_SETGNAME: case JSOP_STRICTSETGNAME: if (script_->hasNonSyntacticScope()) usesEnvironmentChain_ = true; break; case JSOP_FINALLY: hasTryFinally_ = true; break; case JSOP_SETARG: hasSetArg_ = true; break; default: break; } bool jump = IsJumpOpcode(op); if (jump) { // Case instructions do not push the lvalue back when branching. unsigned newStackDepth = stackDepth; if (op == JSOP_CASE) newStackDepth--; unsigned targetOffset = offset + GET_JUMP_OFFSET(pc); // If this is a a backedge to an un-analyzed segment, analyze from there. bool jumpBack = (targetOffset < offset) && !infos_[targetOffset].initialized; infos_[targetOffset].init(newStackDepth); infos_[targetOffset].jumpTarget = true; if (jumpBack) nextpc = script_->offsetToPC(targetOffset); } // Handle any fallthrough from this opcode. if (BytecodeFallsThrough(op)) { jsbytecode* fallthrough = pc + GetBytecodeLength(pc); MOZ_ASSERT(fallthrough < end); unsigned fallthroughOffset = script_->pcToOffset(fallthrough); infos_[fallthroughOffset].init(stackDepth); // Treat the fallthrough of a branch instruction as a jump target. if (jump) infos_[fallthroughOffset].jumpTarget = true; } } return true; }