| /* -*- 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), |
| usesScopeChain_(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 scope chain slot if either the function needs a CallObject |
| // or the script uses the scope chain. The latter case is handled below. |
| usesScopeChain_ = script_->module() || |
| (script_->functionDelazifying() && |
| script_->functionDelazifying()->needsCallObject()); |
| MOZ_ASSERT_IF(script_->hasAnyAliasedBindings(), usesScopeChain_); |
| |
| jsbytecode* end = script_->codeEnd(); |
| |
| // Clear all BytecodeInfo. |
| mozilla::PodZero(infos_.begin(), infos_.length()); |
| infos_[0].init(/*stackDepth=*/0); |
| |
| Vector<CatchFinallyRange, 0, JitAllocPolicy> 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_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: |
| usesScopeChain_ = true; |
| break; |
| |
| case JSOP_GETGNAME: |
| case JSOP_SETGNAME: |
| case JSOP_STRICTSETGNAME: |
| if (script_->hasNonSyntacticScope()) |
| usesScopeChain_ = 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; |
| } |