| /* -*- 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/IonBuilder.h" |
| |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/SizePrintfMacros.h" |
| |
| #include "builtin/Eval.h" |
| #include "builtin/TypedObject.h" |
| #include "frontend/SourceNotes.h" |
| #include "jit/BaselineFrame.h" |
| #include "jit/BaselineInspector.h" |
| #include "jit/Ion.h" |
| #include "jit/IonOptimizationLevels.h" |
| #include "jit/JitSpewer.h" |
| #include "jit/Lowering.h" |
| #include "jit/MIRGraph.h" |
| #include "vm/ArgumentsObject.h" |
| #include "vm/Opcodes.h" |
| #include "vm/RegExpStatics.h" |
| #include "vm/TraceLogging.h" |
| |
| #include "jsopcodeinlines.h" |
| #include "jsscriptinlines.h" |
| |
| #include "jit/CompileInfo-inl.h" |
| #include "jit/shared/Lowering-shared-inl.h" |
| #include "vm/NativeObject-inl.h" |
| #include "vm/ObjectGroup-inl.h" |
| #include "vm/ScopeObject-inl.h" |
| #include "vm/UnboxedObject-inl.h" |
| |
| using namespace js; |
| using namespace js::jit; |
| |
| using mozilla::AssertedCast; |
| using mozilla::DebugOnly; |
| using mozilla::Maybe; |
| |
| using JS::TrackedStrategy; |
| using JS::TrackedOutcome; |
| using JS::TrackedTypeSite; |
| |
| class jit::BaselineFrameInspector |
| { |
| public: |
| TypeSet::Type thisType; |
| JSObject* singletonScopeChain; |
| |
| Vector<TypeSet::Type, 4, JitAllocPolicy> argTypes; |
| Vector<TypeSet::Type, 4, JitAllocPolicy> varTypes; |
| |
| explicit BaselineFrameInspector(TempAllocator* temp) |
| : thisType(TypeSet::UndefinedType()), |
| singletonScopeChain(nullptr), |
| argTypes(*temp), |
| varTypes(*temp) |
| {} |
| }; |
| |
| BaselineFrameInspector* |
| jit::NewBaselineFrameInspector(TempAllocator* temp, BaselineFrame* frame, CompileInfo* info) |
| { |
| MOZ_ASSERT(frame); |
| |
| BaselineFrameInspector* inspector = temp->lifoAlloc()->new_<BaselineFrameInspector>(temp); |
| if (!inspector) |
| return nullptr; |
| |
| // Note: copying the actual values into a temporary structure for use |
| // during compilation could capture nursery pointers, so the values' types |
| // are recorded instead. |
| |
| if (frame->isNonEvalFunctionFrame()) |
| inspector->thisType = TypeSet::GetMaybeUntrackedValueType(frame->thisArgument()); |
| |
| if (frame->scopeChain()->isSingleton()) |
| inspector->singletonScopeChain = frame->scopeChain(); |
| |
| JSScript* script = frame->script(); |
| |
| if (script->functionNonDelazifying()) { |
| if (!inspector->argTypes.reserve(frame->numFormalArgs())) |
| return nullptr; |
| for (size_t i = 0; i < frame->numFormalArgs(); i++) { |
| if (script->formalIsAliased(i)) { |
| inspector->argTypes.infallibleAppend(TypeSet::UndefinedType()); |
| } else if (!script->argsObjAliasesFormals()) { |
| TypeSet::Type type = |
| TypeSet::GetMaybeUntrackedValueType(frame->unaliasedFormal(i)); |
| inspector->argTypes.infallibleAppend(type); |
| } else if (frame->hasArgsObj()) { |
| TypeSet::Type type = |
| TypeSet::GetMaybeUntrackedValueType(frame->argsObj().arg(i)); |
| inspector->argTypes.infallibleAppend(type); |
| } else { |
| inspector->argTypes.infallibleAppend(TypeSet::UndefinedType()); |
| } |
| } |
| } |
| |
| if (!inspector->varTypes.reserve(frame->script()->nfixed())) |
| return nullptr; |
| for (size_t i = 0; i < frame->script()->nfixed(); i++) { |
| if (info->isSlotAliasedAtOsr(i + info->firstLocalSlot())) { |
| inspector->varTypes.infallibleAppend(TypeSet::UndefinedType()); |
| } else { |
| TypeSet::Type type = TypeSet::GetMaybeUntrackedValueType(frame->unaliasedLocal(i)); |
| inspector->varTypes.infallibleAppend(type); |
| } |
| } |
| |
| return inspector; |
| } |
| |
| IonBuilder::IonBuilder(JSContext* analysisContext, CompileCompartment* comp, |
| const JitCompileOptions& options, TempAllocator* temp, |
| MIRGraph* graph, CompilerConstraintList* constraints, |
| BaselineInspector* inspector, CompileInfo* info, |
| const OptimizationInfo* optimizationInfo, |
| BaselineFrameInspector* baselineFrame, size_t inliningDepth, |
| uint32_t loopDepth) |
| : MIRGenerator(comp, options, temp, graph, info, optimizationInfo), |
| backgroundCodegen_(nullptr), |
| actionableAbortScript_(nullptr), |
| actionableAbortPc_(nullptr), |
| actionableAbortMessage_(nullptr), |
| analysisContext(analysisContext), |
| baselineFrame_(baselineFrame), |
| constraints_(constraints), |
| analysis_(*temp, info->script()), |
| thisTypes(nullptr), |
| argTypes(nullptr), |
| typeArray(nullptr), |
| typeArrayHint(0), |
| bytecodeTypeMap(nullptr), |
| loopDepth_(loopDepth), |
| trackedOptimizationSites_(*temp), |
| lexicalCheck_(nullptr), |
| callerResumePoint_(nullptr), |
| callerBuilder_(nullptr), |
| cfgStack_(*temp), |
| loops_(*temp), |
| switches_(*temp), |
| labels_(*temp), |
| iterators_(*temp), |
| loopHeaders_(*temp), |
| inspector(inspector), |
| inliningDepth_(inliningDepth), |
| inlinedBytecodeLength_(0), |
| numLoopRestarts_(0), |
| failedBoundsCheck_(info->script()->failedBoundsCheck()), |
| failedShapeGuard_(info->script()->failedShapeGuard()), |
| failedLexicalCheck_(info->script()->failedLexicalCheck()), |
| nonStringIteration_(false), |
| lazyArguments_(nullptr), |
| inlineCallInfo_(nullptr), |
| maybeFallbackFunctionGetter_(nullptr) |
| { |
| script_ = info->script(); |
| scriptHasIonScript_ = script_->hasIonScript(); |
| pc = info->startPC(); |
| abortReason_ = AbortReason_Disable; |
| |
| MOZ_ASSERT(script()->hasBaselineScript() == (info->analysisMode() != Analysis_ArgumentsUsage)); |
| MOZ_ASSERT(!!analysisContext == (info->analysisMode() == Analysis_DefiniteProperties)); |
| |
| if (!info->isAnalysis()) |
| script()->baselineScript()->setIonCompiledOrInlined(); |
| } |
| |
| void |
| IonBuilder::clearForBackEnd() |
| { |
| MOZ_ASSERT(!analysisContext); |
| baselineFrame_ = nullptr; |
| |
| // The caches below allocate data from the malloc heap. Release this before |
| // later phases of compilation to avoid leaks, as the top level IonBuilder |
| // is not explicitly destroyed. Note that builders for inner scripts are |
| // constructed on the stack and will release this memory on destruction. |
| gsn.purge(); |
| scopeCoordinateNameCache.purge(); |
| } |
| |
| bool |
| IonBuilder::abort(const char* message, ...) |
| { |
| // Don't call PCToLineNumber in release builds. |
| #ifdef JS_JITSPEW |
| va_list ap; |
| va_start(ap, message); |
| abortFmt(message, ap); |
| va_end(ap); |
| # ifdef DEBUG |
| JitSpew(JitSpew_IonAbort, "aborted @ %s:%d", script()->filename(), PCToLineNumber(script(), pc)); |
| # else |
| JitSpew(JitSpew_IonAbort, "aborted @ %s", script()->filename()); |
| # endif |
| #endif |
| trackActionableAbort(message); |
| return false; |
| } |
| |
| IonBuilder* |
| IonBuilder::outermostBuilder() |
| { |
| IonBuilder* builder = this; |
| while (builder->callerBuilder_) |
| builder = builder->callerBuilder_; |
| return builder; |
| } |
| |
| void |
| IonBuilder::trackActionableAbort(const char* message) |
| { |
| if (!isOptimizationTrackingEnabled()) |
| return; |
| |
| IonBuilder* topBuilder = outermostBuilder(); |
| if (topBuilder->hadActionableAbort()) |
| return; |
| |
| topBuilder->actionableAbortScript_ = script(); |
| topBuilder->actionableAbortPc_ = pc; |
| topBuilder->actionableAbortMessage_ = message; |
| } |
| |
| void |
| IonBuilder::spew(const char* message) |
| { |
| // Don't call PCToLineNumber in release builds. |
| #ifdef DEBUG |
| JitSpew(JitSpew_IonMIR, "%s @ %s:%d", message, script()->filename(), PCToLineNumber(script(), pc)); |
| #endif |
| } |
| |
| static inline int32_t |
| GetJumpOffset(jsbytecode* pc) |
| { |
| MOZ_ASSERT(CodeSpec[JSOp(*pc)].type() == JOF_JUMP); |
| return GET_JUMP_OFFSET(pc); |
| } |
| |
| IonBuilder::CFGState |
| IonBuilder::CFGState::If(jsbytecode* join, MTest* test) |
| { |
| CFGState state; |
| state.state = IF_TRUE; |
| state.stopAt = join; |
| state.branch.ifFalse = test->ifFalse(); |
| state.branch.test = test; |
| return state; |
| } |
| |
| IonBuilder::CFGState |
| IonBuilder::CFGState::IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, MTest* test) |
| { |
| MBasicBlock* ifFalse = test->ifFalse(); |
| |
| CFGState state; |
| // If the end of the false path is the same as the start of the |
| // false path, then the "else" block is empty and we can devolve |
| // this to the IF_TRUE case. We handle this here because there is |
| // still an extra GOTO on the true path and we want stopAt to point |
| // there, whereas the IF_TRUE case does not have the GOTO. |
| state.state = (falseEnd == ifFalse->pc()) |
| ? IF_TRUE_EMPTY_ELSE |
| : IF_ELSE_TRUE; |
| state.stopAt = trueEnd; |
| state.branch.falseEnd = falseEnd; |
| state.branch.ifFalse = ifFalse; |
| state.branch.test = test; |
| return state; |
| } |
| |
| IonBuilder::CFGState |
| IonBuilder::CFGState::AndOr(jsbytecode* join, MBasicBlock* lhs) |
| { |
| CFGState state; |
| state.state = AND_OR; |
| state.stopAt = join; |
| state.branch.ifFalse = lhs; |
| state.branch.test = nullptr; |
| return state; |
| } |
| |
| IonBuilder::CFGState |
| IonBuilder::CFGState::TableSwitch(jsbytecode* exitpc, MTableSwitch* ins) |
| { |
| CFGState state; |
| state.state = TABLE_SWITCH; |
| state.stopAt = exitpc; |
| state.tableswitch.exitpc = exitpc; |
| state.tableswitch.breaks = nullptr; |
| state.tableswitch.ins = ins; |
| state.tableswitch.currentBlock = 0; |
| return state; |
| } |
| |
| JSFunction* |
| IonBuilder::getSingleCallTarget(TemporaryTypeSet* calleeTypes) |
| { |
| if (!calleeTypes) |
| return nullptr; |
| |
| JSObject* obj = calleeTypes->maybeSingleton(); |
| if (!obj || !obj->is<JSFunction>()) |
| return nullptr; |
| |
| return &obj->as<JSFunction>(); |
| } |
| |
| bool |
| IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing, |
| ObjectVector& targets, uint32_t maxTargets) |
| { |
| MOZ_ASSERT(targets.empty()); |
| |
| if (!calleeTypes) |
| return true; |
| |
| if (calleeTypes->baseFlags() != 0) |
| return true; |
| |
| unsigned objCount = calleeTypes->getObjectCount(); |
| |
| if (objCount == 0 || objCount > maxTargets) |
| return true; |
| |
| if (!targets.reserve(objCount)) |
| return false; |
| for (unsigned i = 0; i < objCount; i++) { |
| JSObject* obj = calleeTypes->getSingleton(i); |
| if (obj) { |
| MOZ_ASSERT(obj->isSingleton()); |
| } else { |
| ObjectGroup* group = calleeTypes->getGroup(i); |
| if (!group) |
| continue; |
| |
| obj = group->maybeInterpretedFunction(); |
| if (!obj) { |
| targets.clear(); |
| return true; |
| } |
| |
| MOZ_ASSERT(!obj->isSingleton()); |
| } |
| |
| // Don't optimize if the callee is not callable or constructable per |
| // the manner it is being invoked, so that CallKnown does not have to |
| // handle these cases (they will always throw). |
| if (constructing ? !obj->isConstructor() : !obj->isCallable()) { |
| targets.clear(); |
| return true; |
| } |
| |
| targets.infallibleAppend(obj); |
| } |
| |
| return true; |
| } |
| |
| IonBuilder::InliningDecision |
| IonBuilder::DontInline(JSScript* targetScript, const char* reason) |
| { |
| if (targetScript) { |
| JitSpew(JitSpew_Inlining, "Cannot inline %s:%" PRIuSIZE ": %s", |
| targetScript->filename(), targetScript->lineno(), reason); |
| } else { |
| JitSpew(JitSpew_Inlining, "Cannot inline: %s", reason); |
| } |
| |
| return InliningDecision_DontInline; |
| } |
| |
| /* |
| * |hasCommonInliningPath| determines whether the current inlining path has been |
| * seen before based on the sequence of scripts in the chain of |IonBuilder|s. |
| * |
| * An inlining path for a function |f| is the sequence of functions whose |
| * inlinings precede |f| up to any previous occurrences of |f|. |
| * So, if we have the chain of inlinings |
| * |
| * f1 -> f2 -> f -> f3 -> f4 -> f5 -> f |
| * -------- -------------- |
| * |
| * the inlining paths for |f| are [f2, f1] and [f5, f4, f3]. |
| * When attempting to inline |f|, we find all existing inlining paths for |f| |
| * and check whether they share a common prefix with the path created were |f| |
| * inlined. |
| * |
| * For example, given mutually recursive functions |f| and |g|, a possible |
| * inlining is |
| * |
| * +---- Inlining stopped here... |
| * | |
| * v |
| * a -> f -> g -> f \ -> g -> f -> g -> ... |
| * |
| * where the vertical bar denotes the termination of inlining. |
| * Inlining is terminated because we have already observed the inlining path |
| * [f] when inlining function |g|. Note that this will inline recursive |
| * functions such as |fib| only one level, as |fib| has a zero length inlining |
| * path which trivially prefixes all inlining paths. |
| * |
| */ |
| bool |
| IonBuilder::hasCommonInliningPath(const JSScript* scriptToInline) |
| { |
| // Find all previous inlinings of the |scriptToInline| and check for common |
| // inlining paths with the top of the inlining stack. |
| for (IonBuilder* it = this->callerBuilder_; it; it = it->callerBuilder_) { |
| if (it->script() != scriptToInline) |
| continue; |
| |
| // This only needs to check the top of each stack for a match, |
| // as a match of length one ensures a common prefix. |
| IonBuilder* path = it->callerBuilder_; |
| if (!path || this->script() == path->script()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| IonBuilder::InliningDecision |
| IonBuilder::canInlineTarget(JSFunction* target, CallInfo& callInfo) |
| { |
| if (!optimizationInfo().inlineInterpreted()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric); |
| return InliningDecision_DontInline; |
| } |
| |
| if (TraceLogTextIdEnabled(TraceLogger_InlinedScripts)) { |
| return DontInline(nullptr, "Tracelogging of inlined scripts is enabled" |
| "but Tracelogger cannot do that yet."); |
| } |
| |
| if (!target->isInterpreted()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineNotInterpreted); |
| return DontInline(nullptr, "Non-interpreted target"); |
| } |
| |
| if (info().analysisMode() != Analysis_DefiniteProperties) { |
| // If |this| or an argument has an empty resultTypeSet, don't bother |
| // inlining, as the call is currently unreachable due to incomplete type |
| // information. This does not apply to the definite properties analysis, |
| // in that case we want to inline anyway. |
| |
| if (callInfo.thisArg()->emptyResultTypeSet()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineUnreachable); |
| return DontInline(nullptr, "Empty TypeSet for |this|"); |
| } |
| |
| for (size_t i = 0; i < callInfo.argc(); i++) { |
| if (callInfo.getArg(i)->emptyResultTypeSet()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineUnreachable); |
| return DontInline(nullptr, "Empty TypeSet for argument"); |
| } |
| } |
| } |
| |
| // Allow constructing lazy scripts when performing the definite properties |
| // analysis, as baseline has not been used to warm the caller up yet. |
| if (target->isInterpreted() && info().analysisMode() == Analysis_DefiniteProperties) { |
| RootedScript script(analysisContext, target->getOrCreateScript(analysisContext)); |
| if (!script) |
| return InliningDecision_Error; |
| |
| if (!script->hasBaselineScript() && script->canBaselineCompile()) { |
| MethodStatus status = BaselineCompile(analysisContext, script); |
| if (status == Method_Error) |
| return InliningDecision_Error; |
| if (status != Method_Compiled) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineNoBaseline); |
| return InliningDecision_DontInline; |
| } |
| } |
| } |
| |
| if (!target->hasScript()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineLazy); |
| return DontInline(nullptr, "Lazy script"); |
| } |
| |
| JSScript* inlineScript = target->nonLazyScript(); |
| if (callInfo.constructing() && !target->isConstructor()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineNotConstructor); |
| return DontInline(inlineScript, "Callee is not a constructor"); |
| } |
| |
| if (!callInfo.constructing() && target->isClassConstructor()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineClassConstructor); |
| return DontInline(inlineScript, "Not constructing class constructor"); |
| } |
| |
| AnalysisMode analysisMode = info().analysisMode(); |
| if (!CanIonCompile(inlineScript, analysisMode)) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineDisabledIon); |
| return DontInline(inlineScript, "Disabled Ion compilation"); |
| } |
| |
| // Don't inline functions which don't have baseline scripts. |
| if (!inlineScript->hasBaselineScript()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineNoBaseline); |
| return DontInline(inlineScript, "No baseline jitcode"); |
| } |
| |
| if (TooManyFormalArguments(target->nargs())) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineTooManyArgs); |
| return DontInline(inlineScript, "Too many args"); |
| } |
| |
| // We check the number of actual arguments against the maximum number of |
| // formal arguments as we do not want to encode all actual arguments in the |
| // callerResumePoint. |
| if (TooManyFormalArguments(callInfo.argc())) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineTooManyArgs); |
| return DontInline(inlineScript, "Too many actual args"); |
| } |
| |
| if (hasCommonInliningPath(inlineScript)) { |
| trackOptimizationOutcome(TrackedOutcome::HasCommonInliningPath); |
| return DontInline(inlineScript, "Common inlining path"); |
| } |
| |
| if (inlineScript->uninlineable()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric); |
| return DontInline(inlineScript, "Uninlineable script"); |
| } |
| |
| if (inlineScript->needsArgsObj()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineNeedsArgsObj); |
| return DontInline(inlineScript, "Script that needs an arguments object"); |
| } |
| |
| if (inlineScript->isDebuggee()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineDebuggee); |
| return DontInline(inlineScript, "Script is debuggee"); |
| } |
| |
| TypeSet::ObjectKey* targetKey = TypeSet::ObjectKey::get(target); |
| if (targetKey->unknownProperties()) { |
| trackOptimizationOutcome(TrackedOutcome::CantInlineUnknownProps); |
| return DontInline(inlineScript, "Target type has unknown properties"); |
| } |
| |
| return InliningDecision_Inline; |
| } |
| |
| void |
| IonBuilder::popCfgStack() |
| { |
| if (cfgStack_.back().isLoop()) |
| loops_.popBack(); |
| if (cfgStack_.back().state == CFGState::LABEL) |
| labels_.popBack(); |
| cfgStack_.popBack(); |
| } |
| |
| bool |
| IonBuilder::analyzeNewLoopTypes(MBasicBlock* entry, jsbytecode* start, jsbytecode* end) |
| { |
| // The phi inputs at the loop head only reflect types for variables that |
| // were present at the start of the loop. If the variable changes to a new |
| // type within the loop body, and that type is carried around to the loop |
| // head, then we need to know about the new type up front. |
| // |
| // Since SSA information hasn't been constructed for the loop body yet, we |
| // need a separate analysis to pick out the types that might flow around |
| // the loop header. This is a best-effort analysis that may either over- |
| // or under-approximate the set of such types. |
| // |
| // Over-approximating the types may lead to inefficient generated code, and |
| // under-approximating the types will cause the loop body to be analyzed |
| // multiple times as the correct types are deduced (see finishLoop). |
| |
| // If we restarted processing of an outer loop then get loop header types |
| // directly from the last time we have previously processed this loop. This |
| // both avoids repeated work from the bytecode traverse below, and will |
| // also pick up types discovered while previously building the loop body. |
| for (size_t i = 0; i < loopHeaders_.length(); i++) { |
| if (loopHeaders_[i].pc == start) { |
| MBasicBlock* oldEntry = loopHeaders_[i].header; |
| |
| // If this block has been discarded, its resume points will have |
| // already discarded their operands. |
| if (!oldEntry->isDead()) { |
| MResumePoint* oldEntryRp = oldEntry->entryResumePoint(); |
| size_t stackDepth = oldEntryRp->stackDepth(); |
| for (size_t slot = 0; slot < stackDepth; slot++) { |
| MDefinition* oldDef = oldEntryRp->getOperand(slot); |
| if (!oldDef->isPhi()) { |
| MOZ_ASSERT(oldDef->block()->id() < oldEntry->id()); |
| MOZ_ASSERT(oldDef == entry->getSlot(slot)); |
| continue; |
| } |
| MPhi* oldPhi = oldDef->toPhi(); |
| MPhi* newPhi = entry->getSlot(slot)->toPhi(); |
| if (!newPhi->addBackedgeType(oldPhi->type(), oldPhi->resultTypeSet())) |
| return false; |
| } |
| } |
| |
| // Update the most recent header for this loop encountered, in case |
| // new types flow to the phis and the loop is processed at least |
| // three times. |
| loopHeaders_[i].header = entry; |
| return true; |
| } |
| } |
| if (!loopHeaders_.append(LoopHeader(start, entry))) |
| return false; |
| |
| jsbytecode* last = nullptr; |
| jsbytecode* earlier = nullptr; |
| for (jsbytecode* pc = start; pc != end; earlier = last, last = pc, pc += GetBytecodeLength(pc)) { |
| uint32_t slot; |
| if (*pc == JSOP_SETLOCAL) |
| slot = info().localSlot(GET_LOCALNO(pc)); |
| else if (*pc == JSOP_SETARG) |
| slot = info().argSlotUnchecked(GET_ARGNO(pc)); |
| else |
| continue; |
| if (slot >= info().firstStackSlot()) |
| continue; |
| if (!analysis().maybeInfo(pc)) |
| continue; |
| if (!last) |
| continue; |
| |
| MPhi* phi = entry->getSlot(slot)->toPhi(); |
| |
| if (*last == JSOP_POS) |
| last = earlier; |
| |
| if (CodeSpec[*last].format & JOF_TYPESET) { |
| TemporaryTypeSet* typeSet = bytecodeTypes(last); |
| if (!typeSet->empty()) { |
| MIRType type = typeSet->getKnownMIRType(); |
| if (!phi->addBackedgeType(type, typeSet)) |
| return false; |
| } |
| } else if (*last == JSOP_GETLOCAL || *last == JSOP_GETARG) { |
| uint32_t slot = (*last == JSOP_GETLOCAL) |
| ? info().localSlot(GET_LOCALNO(last)) |
| : info().argSlotUnchecked(GET_ARGNO(last)); |
| if (slot < info().firstStackSlot()) { |
| MPhi* otherPhi = entry->getSlot(slot)->toPhi(); |
| if (otherPhi->hasBackedgeType()) { |
| if (!phi->addBackedgeType(otherPhi->type(), otherPhi->resultTypeSet())) |
| return false; |
| } |
| } |
| } else { |
| MIRType type = MIRType_None; |
| switch (*last) { |
| case JSOP_VOID: |
| case JSOP_UNDEFINED: |
| type = MIRType_Undefined; |
| break; |
| case JSOP_GIMPLICITTHIS: |
| if (!script()->hasNonSyntacticScope()) |
| type = MIRType_Undefined; |
| break; |
| case JSOP_NULL: |
| type = MIRType_Null; |
| break; |
| case JSOP_ZERO: |
| case JSOP_ONE: |
| case JSOP_INT8: |
| case JSOP_INT32: |
| case JSOP_UINT16: |
| case JSOP_UINT24: |
| case JSOP_BITAND: |
| case JSOP_BITOR: |
| case JSOP_BITXOR: |
| case JSOP_BITNOT: |
| case JSOP_RSH: |
| case JSOP_LSH: |
| case JSOP_URSH: |
| type = MIRType_Int32; |
| break; |
| case JSOP_FALSE: |
| case JSOP_TRUE: |
| case JSOP_EQ: |
| case JSOP_NE: |
| case JSOP_LT: |
| case JSOP_LE: |
| case JSOP_GT: |
| case JSOP_GE: |
| case JSOP_NOT: |
| case JSOP_STRICTEQ: |
| case JSOP_STRICTNE: |
| case JSOP_IN: |
| case JSOP_INSTANCEOF: |
| type = MIRType_Boolean; |
| break; |
| case JSOP_DOUBLE: |
| type = MIRType_Double; |
| break; |
| case JSOP_STRING: |
| case JSOP_TOSTRING: |
| case JSOP_TYPEOF: |
| case JSOP_TYPEOFEXPR: |
| type = MIRType_String; |
| break; |
| case JSOP_SYMBOL: |
| type = MIRType_Symbol; |
| break; |
| case JSOP_ADD: |
| case JSOP_SUB: |
| case JSOP_MUL: |
| case JSOP_DIV: |
| case JSOP_MOD: |
| case JSOP_NEG: |
| type = inspector->expectedResultType(last); |
| default: |
| break; |
| } |
| if (type != MIRType_None) { |
| if (!phi->addBackedgeType(type, nullptr)) |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool |
| IonBuilder::pushLoop(CFGState::State initial, jsbytecode* stopAt, MBasicBlock* entry, bool osr, |
| 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.continuepc = continuepc; |
| state.loop.entry = entry; |
| state.loop.osr = osr; |
| 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); |
| } |
| |
| bool |
| IonBuilder::init() |
| { |
| if (!TypeScript::FreezeTypeSets(constraints(), script(), &thisTypes, &argTypes, &typeArray)) |
| return false; |
| |
| if (inlineCallInfo_) { |
| // If we're inlining, the actual this/argument types are not necessarily |
| // a subset of the script's observed types. |argTypes| is never accessed |
| // for inlined scripts, so we just null it. |
| thisTypes = inlineCallInfo_->thisArg()->resultTypeSet(); |
| argTypes = nullptr; |
| } |
| |
| if (!analysis().init(alloc(), gsn)) |
| return false; |
| |
| // The baseline script normally has the bytecode type map, but compute |
| // it ourselves if we do not have a baseline script. |
| if (script()->hasBaselineScript()) { |
| bytecodeTypeMap = script()->baselineScript()->bytecodeTypeMap(); |
| } else { |
| bytecodeTypeMap = alloc_->lifoAlloc()->newArrayUninitialized<uint32_t>(script()->nTypeSets()); |
| if (!bytecodeTypeMap) |
| return false; |
| FillBytecodeTypeMap(script(), bytecodeTypeMap); |
| } |
| |
| return true; |
| } |
| |
| bool |
| IonBuilder::build() |
| { |
| if (!init()) |
| return false; |
| |
| if (script()->hasBaselineScript()) |
| script()->baselineScript()->resetMaxInliningDepth(); |
| |
| if (!setCurrentAndSpecializePhis(newBlock(pc))) |
| return false; |
| if (!current) |
| return false; |
| |
| #ifdef JS_JITSPEW |
| if (info().isAnalysis()) { |
| JitSpew(JitSpew_IonScripts, "Analyzing script %s:%" PRIuSIZE " (%p) %s", |
| script()->filename(), script()->lineno(), (void*)script(), |
| AnalysisModeString(info().analysisMode())); |
| } else { |
| JitSpew(JitSpew_IonScripts, "%sompiling script %s:%" PRIuSIZE " (%p) (warmup-counter=%" PRIu32 ", level=%s)", |
| (script()->hasIonScript() ? "Rec" : "C"), |
| script()->filename(), script()->lineno(), (void*)script(), |
| script()->getWarmUpCount(), OptimizationLevelString(optimizationInfo().level())); |
| } |
| #endif |
| |
| initParameters(); |
| initLocals(); |
| |
| // Initialize something for the scope chain. We can bail out before the |
| // start instruction, but the snapshot is encoded *at* the start |
| // instruction, which means generating any code that could load into |
| // registers is illegal. |
| MInstruction* scope = MConstant::New(alloc(), UndefinedValue()); |
| current->add(scope); |
| current->initSlot(info().scopeChainSlot(), scope); |
| |
| // Initialize the return value. |
| MInstruction* returnValue = MConstant::New(alloc(), UndefinedValue()); |
| current->add(returnValue); |
| current->initSlot(info().returnValueSlot(), returnValue); |
| |
| // Initialize the arguments object slot to undefined if necessary. |
| if (info().hasArguments()) { |
| MInstruction* argsObj = MConstant::New(alloc(), UndefinedValue()); |
| current->add(argsObj); |
| current->initSlot(info().argsObjSlot(), argsObj); |
| } |
| |
| // Emit the start instruction, so we can begin real instructions. |
| current->add(MStart::New(alloc(), MStart::StartType_Default)); |
| |
| // Guard against over-recursion. Do this before we start unboxing, since |
| // this will create an OSI point that will read the incoming argument |
| // values, which is nice to do before their last real use, to minimize |
| // register/stack pressure. |
| MCheckOverRecursed* check = MCheckOverRecursed::New(alloc()); |
| current->add(check); |
| MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint()); |
| if (!entryRpCopy) |
| return false; |
| check->setResumePoint(entryRpCopy); |
| |
| // Parameters have been checked to correspond to the typeset, now we unbox |
| // what we can in an infallible manner. |
| rewriteParameters(); |
| |
| // Check for redeclaration errors for global scripts. |
| if (!info().funMaybeLazy() && !info().module() && |
| script()->bindings.numBodyLevelLocals() > 0) |
| { |
| MGlobalNameConflictsCheck* redeclCheck = MGlobalNameConflictsCheck::New(alloc()); |
| current->add(redeclCheck); |
| MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint()); |
| if (!entryRpCopy) |
| return false; |
| redeclCheck->setResumePoint(entryRpCopy); |
| } |
| |
| // It's safe to start emitting actual IR, so now build the scope chain. |
| if (!initScopeChain()) |
| return false; |
| |
| if (info().needsArgsObj() && !initArgumentsObject()) |
| return false; |
| |
| // The type analysis phase attempts to insert unbox operations near |
| // definitions of values. It also attempts to replace uses in resume points |
| // with the narrower, unboxed variants. However, we must prevent this |
| // replacement from happening on values in the entry snapshot. Otherwise we |
| // could get this: |
| // |
| // v0 = MParameter(0) |
| // v1 = MParameter(1) |
| // -- ResumePoint(v2, v3) |
| // v2 = Unbox(v0, INT32) |
| // v3 = Unbox(v1, INT32) |
| // |
| // So we attach the initial resume point to each parameter, which the type |
| // analysis explicitly checks (this is the same mechanism used for |
| // effectful operations). |
| for (uint32_t i = 0; i < info().endArgSlot(); i++) { |
| MInstruction* ins = current->getEntrySlot(i)->toInstruction(); |
| if (ins->type() != MIRType_Value) |
| continue; |
| |
| MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint()); |
| if (!entryRpCopy) |
| return false; |
| ins->setResumePoint(entryRpCopy); |
| } |
| |
| // lazyArguments should never be accessed in |argsObjAliasesFormals| scripts. |
| if (info().hasArguments() && !info().argsObjAliasesFormals()) { |
| lazyArguments_ = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS)); |
| current->add(lazyArguments_); |
| } |
| |
| insertRecompileCheck(); |
| |
| if (!traverseBytecode()) |
| return false; |
| |
| // Discard unreferenced & pre-allocated resume points. |
| replaceMaybeFallbackFunctionGetter(nullptr); |
| |
| if (script_->hasBaselineScript() && |
| inlinedBytecodeLength_ > script_->baselineScript()->inlinedBytecodeLength()) |
| { |
| script_->baselineScript()->setInlinedBytecodeLength(inlinedBytecodeLength_); |
| } |
| |
| if (!maybeAddOsrTypeBarriers()) |
| return false; |
| |
| if (!processIterators()) |
| return false; |
| |
| if (!info().isAnalysis() && !abortedPreliminaryGroups().empty()) { |
| abortReason_ = AbortReason_PreliminaryObjects; |
| return false; |
| } |
| |
| if (shouldForceAbort()) { |
| abortReason_ = AbortReason_Disable; |
| return false; |
| } |
| |
| MOZ_ASSERT(loopDepth_ == 0); |
| abortReason_ = AbortReason_NoAbort; |
| return true; |
| } |
| |
| bool |
| IonBuilder::processIterators() |
| { |
| // Find phis that must directly hold an iterator live. |
| Vector<MPhi*, 0, SystemAllocPolicy> worklist; |
| for (size_t i = 0; i < iterators_.length(); i++) { |
| MInstruction* ins = iterators_[i]; |
| for (MUseDefIterator iter(ins); iter; iter++) { |
| if (iter.def()->isPhi()) { |
| if (!worklist.append(iter.def()->toPhi())) |
| return false; |
| } |
| } |
| } |
| |
| // Propagate the iterator and live status of phis to all other connected |
| // phis. |
| while (!worklist.empty()) { |
| MPhi* phi = worklist.popCopy(); |
| phi->setIterator(); |
| phi->setImplicitlyUsedUnchecked(); |
| |
| for (MUseDefIterator iter(phi); iter; iter++) { |
| if (iter.def()->isPhi()) { |
| MPhi* other = iter.def()->toPhi(); |
| if (!other->isIterator() && !worklist.append(other)) |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| bool |
| IonBuilder::buildInline(IonBuilder* callerBuilder, MResumePoint* callerResumePoint, |
| CallInfo& callInfo) |
| { |
| inlineCallInfo_ = &callInfo; |
| |
| if (!init()) |
| return false; |
| |
| JitSpew(JitSpew_IonScripts, "Inlining script %s:%" PRIuSIZE " (%p)", |
| script()->filename(), script()->lineno(), (void*)script()); |
| |
| callerBuilder_ = callerBuilder; |
| callerResumePoint_ = callerResumePoint; |
| |
| if (callerBuilder->failedBoundsCheck_) |
| failedBoundsCheck_ = true; |
| |
| if (callerBuilder->failedShapeGuard_) |
| failedShapeGuard_ = true; |
| |
| if (callerBuilder->failedLexicalCheck_) |
| failedLexicalCheck_ = true; |
| |
| safeForMinorGC_ = callerBuilder->safeForMinorGC_; |
| |
| // Generate single entrance block. |
| if (!setCurrentAndSpecializePhis(newBlock(pc))) |
| return false; |
| if (!current) |
| return false; |
| |
| current->setCallerResumePoint(callerResumePoint); |
| |
| // Connect the entrance block to the last block in the caller's graph. |
| MBasicBlock* predecessor = callerBuilder->current; |
| MOZ_ASSERT(predecessor == callerResumePoint->block()); |
| |
| predecessor->end(MGoto::New(alloc(), current)); |
| if (!current->addPredecessorWithoutPhis(predecessor)) |
| return false; |
| |
| // Initialize scope chain slot to Undefined. It's set later by |initScopeChain|. |
| MInstruction* scope = MConstant::New(alloc(), UndefinedValue()); |
| current->add(scope); |
| current->initSlot(info().scopeChainSlot(), scope); |
| |
| // Initialize |return value| slot. |
| MInstruction* returnValue = MConstant::New(alloc(), UndefinedValue()); |
| current->add(returnValue); |
| current->initSlot(info().returnValueSlot(), returnValue); |
| |
| // Initialize |arguments| slot. |
| if (info().hasArguments()) { |
| MInstruction* argsObj = MConstant::New(alloc(), UndefinedValue()); |
| current->add(argsObj); |
| current->initSlot(info().argsObjSlot(), argsObj); |
| } |
| |
| // Initialize |this| slot. |
| current->initSlot(info().thisSlot(), callInfo.thisArg()); |
| |
| JitSpew(JitSpew_Inlining, "Initializing %u arg slots", info().nargs()); |
| |
| // NB: Ion does not inline functions which |needsArgsObj|. So using argSlot() |
| // instead of argSlotUnchecked() below is OK |
| MOZ_ASSERT(!info().needsArgsObj()); |
| |
| // Initialize actually set arguments. |
| uint32_t existing_args = Min<uint32_t>(callInfo.argc(), info().nargs()); |
| for (size_t i = 0; i < existing_args; ++i) { |
| MDefinition* arg = callInfo.getArg(i); |
| current->initSlot(info().argSlot(i), arg); |
| } |
| |
| // Pass Undefined for missing arguments |
| for (size_t i = callInfo.argc(); i < info().nargs(); ++i) { |
| MConstant* arg = MConstant::New(alloc(), UndefinedValue()); |
| current->add(arg); |
| current->initSlot(info().argSlot(i), arg); |
| } |
| |
| // Initialize the scope chain now that args are initialized. |
| if (!initScopeChain(callInfo.fun())) |
| return false; |
| |
| JitSpew(JitSpew_Inlining, "Initializing %u local slots; fixed lexicals begin at %u", |
| info().nlocals(), info().fixedLexicalBegin()); |
| |
| initLocals(); |
| |
| JitSpew(JitSpew_Inlining, "Inline entry block MResumePoint %p, %u stack slots", |
| (void*) current->entryResumePoint(), current->entryResumePoint()->stackDepth()); |
| |
| // +2 for the scope chain and |this|, maybe another +1 for arguments object slot. |
| MOZ_ASSERT(current->entryResumePoint()->stackDepth() == info().totalSlots()); |
| |
| if (script_->argumentsHasVarBinding()) { |
| lazyArguments_ = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS)); |
| current->add(lazyArguments_); |
| } |
| |
| insertRecompileCheck(); |
| |
| if (!traverseBytecode()) |
| return false; |
| |
| // Discard unreferenced & pre-allocated resume points. |
| replaceMaybeFallbackFunctionGetter(nullptr); |
| |
| if (!info().isAnalysis() && !abortedPreliminaryGroups().empty()) { |
| abortReason_ = AbortReason_PreliminaryObjects; |
| return false; |
| } |
| |
| if (shouldForceAbort()) { |
| abortReason_ = AbortReason_Disable; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void |
| IonBuilder::rewriteParameter(uint32_t slotIdx, MDefinition* param, int32_t argIndex) |
| { |
| MOZ_ASSERT(param->isParameter() || param->isGetArgumentsObjectArg()); |
| |
| TemporaryTypeSet* types = param->resultTypeSet(); |
| MDefinition* actual = ensureDefiniteType(param, types->getKnownMIRType()); |
| if (actual == param) |
| return; |
| |
| // Careful! We leave the original MParameter in the entry resume point. The |
| // arguments still need to be checked unless proven otherwise at the call |
| // site, and these checks can bailout. We can end up: |
| // v0 = Parameter(0) |
| // v1 = Unbox(v0, INT32) |
| // -- ResumePoint(v0) |
| // |
| // As usual, it would be invalid for v1 to be captured in the initial |
| // resume point, rather than v0. |
| current->rewriteSlot(slotIdx, actual); |
| } |
| |
| // Apply Type Inference information to parameters early on, unboxing them if |
| // they have a definitive type. The actual guards will be emitted by the code |
| // generator, explicitly, as part of the function prologue. |
| void |
| IonBuilder::rewriteParameters() |
| { |
| MOZ_ASSERT(info().scopeChainSlot() == 0); |
| |
| if (!info().funMaybeLazy()) |
| return; |
| |
| for (uint32_t i = info().startArgSlot(); i < info().endArgSlot(); i++) { |
| MDefinition* param = current->getSlot(i); |
| rewriteParameter(i, param, param->toParameter()->index()); |
| } |
| } |
| |
| void |
| IonBuilder::initParameters() |
| { |
| if (!info().funMaybeLazy()) |
| return; |
| |
| // If we are doing OSR on a frame which initially executed in the |
| // interpreter and didn't accumulate type information, try to use that OSR |
| // frame to determine possible initial types for 'this' and parameters. |
| |
| if (thisTypes->empty() && baselineFrame_) { |
| TypeSet::Type type = baselineFrame_->thisType; |
| if (type.isSingletonUnchecked()) |
| checkNurseryObject(type.singleton()); |
| thisTypes->addType(type, alloc_->lifoAlloc()); |
| } |
| |
| MParameter* param = MParameter::New(alloc(), MParameter::THIS_SLOT, thisTypes); |
| current->add(param); |
| current->initSlot(info().thisSlot(), param); |
| |
| for (uint32_t i = 0; i < info().nargs(); i++) { |
| TemporaryTypeSet* types = &argTypes[i]; |
| if (types->empty() && baselineFrame_ && |
| !script_->baselineScript()->modifiesArguments()) |
| { |
| TypeSet::Type type = baselineFrame_->argTypes[i]; |
| if (type.isSingletonUnchecked()) |
| checkNurseryObject(type.singleton()); |
| types->addType(type, alloc_->lifoAlloc()); |
| } |
| |
| param = MParameter::New(alloc(), i, types); |
| current->add(param); |
| current->initSlot(info().argSlotUnchecked(i), param); |
| } |
| } |
| |
| void |
| IonBuilder::initLocals() |
| { |
| if (info().nlocals() == 0) |
| return; |
| |
| MConstant* undef = nullptr; |
| if (info().fixedLexicalBegin() > 0) { |
| undef = MConstant::New(alloc(), UndefinedValue()); |
| current->add(undef); |
| } |
| |
| MConstant* uninitLexical = nullptr; |
| if (info().fixedLexicalBegin() < info().nlocals()) { |
| uninitLexical = MConstant::New(alloc(), MagicValue(JS_UNINITIALIZED_LEXICAL)); |
| current->add(uninitLexical); |
| } |
| |
| for (uint32_t i = 0; i < info().nlocals(); i++) { |
| current->initSlot(info().localSlot(i), (i < info().fixedLexicalBegin() |
| ? undef |
| : uninitLexical)); |
| } |
| } |
| |
| bool |
| IonBuilder::initScopeChain(MDefinition* callee) |
| { |
| MInstruction* scope = nullptr; |
| |
| // If the script doesn't use the scopechain, then it's already initialized |
| // from earlier. However, always make a scope chain when |needsArgsObj| is true |
| // for the script, since arguments object construction requires the scope chain |
| // to be passed in. |
| if (!info().needsArgsObj() && !analysis().usesScopeChain()) |
| return true; |
| |
| // The scope chain is only tracked in scripts that have NAME opcodes which |
| // will try to access the scope. For other scripts, the scope instructions |
| // will be held live by resume points and code will still be generated for |
| // them, so just use a constant undefined value. |
| |
| if (JSFunction* fun = info().funMaybeLazy()) { |
| if (!callee) { |
| MCallee* calleeIns = MCallee::New(alloc()); |
| current->add(calleeIns); |
| callee = calleeIns; |
| } |
| scope = MFunctionEnvironment::New(alloc(), callee); |
| current->add(scope); |
| |
| // This reproduce what is done in CallObject::createForFunction. Skip |
| // this for analyses, as the script might not have a baseline script |
| // with template objects yet. |
| if (fun->needsCallObject() && !info().isAnalysis()) { |
| if (fun->isNamedLambda()) { |
| scope = createDeclEnvObject(callee, scope); |
| if (!scope) |
| return false; |
| } |
| |
| scope = createCallObject(callee, scope); |
| if (!scope) |
| return false; |
| } |
| } else if (ModuleObject* module = info().module()) { |
| // Modules use a pre-created scope object. |
| scope = constant(ObjectValue(module->initialEnvironment())); |
| } else { |
| // For global scripts without a non-syntactic global scope, the scope |
| // chain is the global lexical scope. |
| MOZ_ASSERT(!script()->isForEval()); |
| MOZ_ASSERT(!script()->hasNonSyntacticScope()); |
| scope = constant(ObjectValue(script()->global().lexicalScope())); |
| } |
| |
| current->setScopeChain(scope); |
| return true; |
| } |
| |
| bool |
| IonBuilder::initArgumentsObject() |
| { |
| JitSpew(JitSpew_IonMIR, "%s:%" PRIuSIZE " - Emitting code to initialize arguments object! block=%p", |
| script()->filename(), script()->lineno(), current); |
| MOZ_ASSERT(info().needsArgsObj()); |
| MCreateArgumentsObject* argsObj = MCreateArgumentsObject::New(alloc(), current->scopeChain()); |
| current->add(argsObj); |
| current->setArgumentsObject(argsObj); |
| return true; |
| } |
| |
| bool |
| IonBuilder::addOsrValueTypeBarrier(uint32_t slot, MInstruction** def_, |
| MIRType type, TemporaryTypeSet* typeSet) |
| { |
| MInstruction*& def = *def_; |
| MBasicBlock* osrBlock = def->block(); |
| |
| // Clear bogus type information added in newOsrPreheader(). |
| def->setResultType(MIRType_Value); |
| def->setResultTypeSet(nullptr); |
| |
| if (typeSet && !typeSet->unknown()) { |
| MInstruction* barrier = MTypeBarrier::New(alloc(), def, typeSet); |
| osrBlock->insertBefore(osrBlock->lastIns(), barrier); |
| osrBlock->rewriteSlot(slot, barrier); |
| def = barrier; |
| } else if (type == MIRType_Null || |
| type == MIRType_Undefined || |
| type == MIRType_MagicOptimizedArguments) |
| { |
| // No unbox instruction will be added below, so check the type by |
| // adding a type barrier for a singleton type set. |
| TypeSet::Type ntype = TypeSet::PrimitiveType(ValueTypeFromMIRType(type)); |
| LifoAlloc* lifoAlloc = alloc().lifoAlloc(); |
| typeSet = lifoAlloc->new_<TemporaryTypeSet>(lifoAlloc, ntype); |
| if (!typeSet) |
| return false; |
| MInstruction* barrier = MTypeBarrier::New(alloc(), def, typeSet); |
| osrBlock->insertBefore(osrBlock->lastIns(), barrier); |
| osrBlock->rewriteSlot(slot, barrier); |
| def = barrier; |
| } |
| |
| switch (type) { |
| case MIRType_Boolean: |
| case MIRType_Int32: |
| case MIRType_Double: |
| case MIRType_String: |
| case MIRType_Symbol: |
| case MIRType_Object: |
| if (type != def->type()) { |
| MUnbox* unbox = MUnbox::New(alloc(), def, type, MUnbox::Fallible); |
| osrBlock->insertBefore(osrBlock->lastIns(), unbox); |
| osrBlock->rewriteSlot(slot, unbox); |
| def = unbox; |
| } |
| break; |
| |
| case MIRType_Null: |
| { |
| MConstant* c = MConstant::New(alloc(), NullValue()); |
| osrBlock->insertBefore(osrBlock->lastIns(), c); |
| osrBlock->rewriteSlot(slot, c); |
| def = c; |
| break; |
| } |
| |
| case MIRType_Undefined: |
| { |
| MConstant* c = MConstant::New(alloc(), UndefinedValue()); |
| osrBlock->insertBefore(osrBlock->lastIns(), c); |
| osrBlock->rewriteSlot(slot, c); |
| def = c; |
| break; |
| } |
| |
| case MIRType_MagicOptimizedArguments: |
| MOZ_ASSERT(lazyArguments_); |
| osrBlock->rewriteSlot(slot, lazyArguments_); |
| def = lazyArguments_; |
| break; |
| |
| default: |
| break; |
| } |
| |
| MOZ_ASSERT(def == osrBlock->getSlot(slot)); |
| return true; |
| } |
| |
| bool |
| IonBuilder::maybeAddOsrTypeBarriers() |
| { |
| if (!info().osrPc()) |
| return true; |
| |
| // The loop has successfully been processed, and the loop header phis |
| // have their final type. Add unboxes and type barriers in the OSR |
| // block to check that the values have the appropriate type, and update |
| // the types in the preheader. |
| |
| MBasicBlock* osrBlock = graph().osrBlock(); |
| if (!osrBlock) { |
| // Because IonBuilder does not compile catch blocks, it's possible to |
| // end up without an OSR block if the OSR pc is only reachable via a |
| // break-statement inside the catch block. For instance: |
| // |
| // for (;;) { |
| // try { |
| // throw 3; |
| // } catch(e) { |
| // break; |
| // } |
| // } |
| // while (..) { } // <= OSR here, only reachable via catch block. |
| // |
| // For now we just abort in this case. |
| MOZ_ASSERT(graph().hasTryBlock()); |
| return abort("OSR block only reachable through catch block"); |
| } |
| |
| MBasicBlock* preheader = osrBlock->getSuccessor(0); |
| MBasicBlock* header = preheader->getSuccessor(0); |
| static const size_t OSR_PHI_POSITION = 1; |
| MOZ_ASSERT(preheader->getPredecessor(OSR_PHI_POSITION) == osrBlock); |
| |
| MResumePoint* headerRp = header->entryResumePoint(); |
| size_t stackDepth = headerRp->stackDepth(); |
| MOZ_ASSERT(stackDepth == osrBlock->stackDepth()); |
| for (uint32_t slot = info().startArgSlot(); slot < stackDepth; slot++) { |
| // Aliased slots are never accessed, since they need to go through |
| // the callobject. The typebarriers are added there and can be |
| // discarded here. |
| if (info().isSlotAliasedAtOsr(slot)) |
| continue; |
| |
| MInstruction* def = osrBlock->getSlot(slot)->toInstruction(); |
| MPhi* preheaderPhi = preheader->getSlot(slot)->toPhi(); |
| MPhi* headerPhi = headerRp->getOperand(slot)->toPhi(); |
| |
| MIRType type = headerPhi->type(); |
| TemporaryTypeSet* typeSet = headerPhi->resultTypeSet(); |
| |
| if (!addOsrValueTypeBarrier(slot, &def, type, typeSet)) |
| return false; |
| |
| preheaderPhi->replaceOperand(OSR_PHI_POSITION, def); |
| preheaderPhi->setResultType(type); |
| preheaderPhi->setResultTypeSet(typeSet); |
| } |
| |
| return true; |
| } |
| |
| // We try to build a control-flow graph in the order that it would be built as |
| // if traversing the AST. This leads to a nice ordering and lets us build SSA |
| // in one pass, since the bytecode is structured. |
| // |
| // We traverse the bytecode iteratively, maintaining a current basic block. |
| // Each basic block has a mapping of local slots to instructions, as well as a |
| // stack depth. As we encounter instructions we mutate this mapping in the |
| // current block. |
| // |
| // Things get interesting when we encounter a control structure. This can be |
| // either an IFEQ, downward GOTO, or a decompiler hint stashed away in source |
| // notes. Once we encounter such an opcode, we recover the structure of the |
| // control flow (its branches and bounds), and push it on a stack. |
| // |
| // As we continue traversing the bytecode, we look for points that would |
| // terminate the topmost control flow path pushed on the stack. These are: |
| // (1) The bounds of the current structure (end of a loop or join/edge of a |
| // branch). |
| // (2) A "return", "break", or "continue" statement. |
| // |
| // For (1), we expect that there is a current block in the progress of being |
| // built, and we complete the necessary edges in the CFG. For (2), we expect |
| // that there is no active block. |
| // |
| // For normal diamond join points, we construct Phi nodes as we add |
| // predecessors. For loops, care must be taken to propagate Phi nodes back |
| // through uses in the loop body. |
| bool |
| IonBuilder::traverseBytecode() |
| { |
| for (;;) { |
| MOZ_ASSERT(pc < info().limitPC()); |
| |
| for (;;) { |
| if (!alloc().ensureBallast()) |
| return false; |
| |
| // Check if we've hit an expected join point or edge in the bytecode. |
| // Leaving one control structure could place us at the edge of another, |
| // thus |while| instead of |if| so we don't skip any opcodes. |
| if (!cfgStack_.empty() && cfgStack_.back().stopAt == pc) { |
| ControlStatus status = processCfgStack(); |
| if (status == ControlStatus_Error) |
| return false; |
| if (status == ControlStatus_Abort) |
| return abort("Aborted while processing control flow"); |
| if (!current) |
| return true; |
| continue; |
| } |
| |
| // Some opcodes need to be handled early because they affect control |
| // flow, terminating the current basic block and/or instructing the |
| // traversal algorithm to continue from a new pc. |
| // |
| // (1) If the opcode does not affect control flow, then the opcode |
| // is inspected and transformed to IR. This is the process_opcode |
| // label. |
| // (2) A loop could be detected via a forward GOTO. In this case, |
| // we don't want to process the GOTO, but the following |
| // instruction. |
| // (3) A RETURN, STOP, BREAK, or CONTINUE may require processing the |
| // CFG stack to terminate open branches. |
| // |
| // Similar to above, snooping control flow could land us at another |
| // control flow point, so we iterate until it's time to inspect a real |
| // opcode. |
| ControlStatus status; |
| if ((status = snoopControlFlow(JSOp(*pc))) == ControlStatus_None) |
| break; |
| if (status == ControlStatus_Error) |
| return false; |
| if (status == ControlStatus_Abort) |
| return abort("Aborted while processing control flow"); |
| if (!current) |
| return true; |
| } |
| |
| #ifdef DEBUG |
| // In debug builds, after compiling this op, check that all values |
| // popped by this opcode either: |
| // |
| // (1) Have the ImplicitlyUsed flag set on them. |
| // (2) Have more uses than before compiling this op (the value is |
| // used as operand of a new MIR instruction). |
| // |
| // This is used to catch problems where IonBuilder pops a value without |
| // adding any SSA uses and doesn't call setImplicitlyUsedUnchecked on it. |
| Vector<MDefinition*, 4, JitAllocPolicy> popped(alloc()); |
| Vector<size_t, 4, JitAllocPolicy> poppedUses(alloc()); |
| unsigned nuses = GetUseCount(script_, script_->pcToOffset(pc)); |
| |
| for (unsigned i = 0; i < nuses; i++) { |
| MDefinition* def = current->peek(-int32_t(i + 1)); |
| if (!popped.append(def) || !poppedUses.append(def->defUseCount())) |
| return false; |
| } |
| #endif |
| |
| // Nothing in inspectOpcode() is allowed to advance the pc. |
| JSOp op = JSOp(*pc); |
| if (!inspectOpcode(op)) |
| return false; |
| |
| #ifdef DEBUG |
| for (size_t i = 0; i < popped.length(); i++) { |
| switch (op) { |
| case JSOP_POP: |
| case JSOP_POPN: |
| case JSOP_DUPAT: |
| case JSOP_DUP: |
| case JSOP_DUP2: |
| case JSOP_PICK: |
| case JSOP_SWAP: |
| case JSOP_SETARG: |
| case JSOP_SETLOCAL: |
| case JSOP_INITLEXICAL: |
| case JSOP_SETRVAL: |
| case JSOP_VOID: |
| // Don't require SSA uses for values popped by these ops. |
| break; |
| |
| case JSOP_POS: |
| case JSOP_TOID: |
| case JSOP_TOSTRING: |
| // These ops may leave their input on the stack without setting |
| // the ImplicitlyUsed flag. If this value will be popped immediately, |
| // we may replace it with |undefined|, but the difference is |
| // not observable. |
| MOZ_ASSERT(i == 0); |
| if (current->peek(-1) == popped[0]) |
| break; |
| // FALL THROUGH |
| |
| default: |
| MOZ_ASSERT(popped[i]->isImplicitlyUsed() || |
| |
| // MNewDerivedTypedObject instances are |
| // often dead unless they escape from the |
| // fn. See IonBuilder::loadTypedObjectData() |
| // for more details. |
| popped[i]->isNewDerivedTypedObject() || |
| |
| popped[i]->defUseCount() > poppedUses[i]); |
| break; |
| } |
| } |
| #endif |
| |
| pc += CodeSpec[op].length; |
| current->updateTrackedSite(bytecodeSite(pc)); |
| } |
| |
| return true; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::snoopControlFlow(JSOp op) |
| { |
| switch (op) { |
| case JSOP_NOP: |
| return maybeLoop(op, info().getNote(gsn, pc)); |
| |
| case JSOP_POP: |
| return maybeLoop(op, info().getNote(gsn, pc)); |
| |
| case JSOP_RETURN: |
| case JSOP_RETRVAL: |
| return processReturn(op); |
| |
| case JSOP_THROW: |
| return processThrow(); |
| |
| case JSOP_GOTO: |
| { |
| jssrcnote* sn = info().getNote(gsn, 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: |
| // while (cond) { } |
| return whileOrForInLoop(sn); |
| |
| default: |
| // Hard assert for now - make an error later. |
| MOZ_CRASH("unknown goto case"); |
| } |
| break; |
| } |
| |
| case JSOP_TABLESWITCH: |
| return tableSwitch(op, info().getNote(gsn, pc)); |
| |
| case JSOP_IFNE: |
| // We should never reach an IFNE, it's a stopAt point, which will |
| // trigger closing the loop. |
| MOZ_CRASH("we should never reach an ifne!"); |
| |
| default: |
| break; |
| } |
| return ControlStatus_None; |
| } |
| |
| bool |
| IonBuilder::inspectOpcode(JSOp op) |
| { |
| MOZ_ASSERT(analysis_.maybeInfo(pc), "Compiling unreachable op"); |
| |
| switch (op) { |
| case JSOP_NOP: |
| case JSOP_LINENO: |
| case JSOP_LOOPENTRY: |
| return true; |
| |
| case JSOP_LABEL: |
| return jsop_label(); |
| |
| case JSOP_UNDEFINED: |
| // If this ever changes, change what JSOP_GIMPLICITTHIS does too. |
| return pushConstant(UndefinedValue()); |
| |
| case JSOP_IFEQ: |
| return jsop_ifeq(JSOP_IFEQ); |
| |
| case JSOP_TRY: |
| return jsop_try(); |
| |
| case JSOP_CONDSWITCH: |
| return jsop_condswitch(); |
| |
| case JSOP_BITNOT: |
| return jsop_bitnot(); |
| |
| case JSOP_BITAND: |
| case JSOP_BITOR: |
| case JSOP_BITXOR: |
| case JSOP_LSH: |
| case JSOP_RSH: |
| case JSOP_URSH: |
| return jsop_bitop(op); |
| |
| case JSOP_ADD: |
| case JSOP_SUB: |
| case JSOP_MUL: |
| case JSOP_DIV: |
| case JSOP_MOD: |
| return jsop_binary_arith(op); |
| |
| case JSOP_POW: |
| return jsop_pow(); |
| |
| case JSOP_POS: |
| return jsop_pos(); |
| |
| case JSOP_NEG: |
| return jsop_neg(); |
| |
| case JSOP_TOSTRING: |
| return jsop_tostring(); |
| |
| case JSOP_AND: |
| case JSOP_OR: |
| return jsop_andor(op); |
| |
| case JSOP_DEFVAR: |
| return jsop_defvar(GET_UINT32_INDEX(pc)); |
| |
| case JSOP_DEFLET: |
| case JSOP_DEFCONST: |
| return jsop_deflexical(GET_UINT32_INDEX(pc)); |
| |
| case JSOP_DEFFUN: |
| return jsop_deffun(GET_UINT32_INDEX(pc)); |
| |
| case JSOP_EQ: |
| case JSOP_NE: |
| case JSOP_STRICTEQ: |
| case JSOP_STRICTNE: |
| case JSOP_LT: |
| case JSOP_LE: |
| case JSOP_GT: |
| case JSOP_GE: |
| return jsop_compare(op); |
| |
| case JSOP_DOUBLE: |
| return pushConstant(info().getConst(pc)); |
| |
| case JSOP_STRING: |
| return pushConstant(StringValue(info().getAtom(pc))); |
| |
| case JSOP_SYMBOL: { |
| unsigned which = GET_UINT8(pc); |
| JS::Symbol* sym = compartment->runtime()->wellKnownSymbols().get(which); |
| return pushConstant(SymbolValue(sym)); |
| } |
| |
| case JSOP_ZERO: |
| return pushConstant(Int32Value(0)); |
| |
| case JSOP_ONE: |
| return pushConstant(Int32Value(1)); |
| |
| case JSOP_NULL: |
| return pushConstant(NullValue()); |
| |
| case JSOP_VOID: |
| current->pop(); |
| return pushConstant(UndefinedValue()); |
| |
| case JSOP_HOLE: |
| return pushConstant(MagicValue(JS_ELEMENTS_HOLE)); |
| |
| case JSOP_FALSE: |
| return pushConstant(BooleanValue(false)); |
| |
| case JSOP_TRUE: |
| return pushConstant(BooleanValue(true)); |
| |
| case JSOP_ARGUMENTS: |
| return jsop_arguments(); |
| |
| case JSOP_RUNONCE: |
| return jsop_runonce(); |
| |
| case JSOP_REST: |
| return jsop_rest(); |
| |
| case JSOP_GETARG: |
| if (info().argsObjAliasesFormals()) { |
| MGetArgumentsObjectArg* getArg = MGetArgumentsObjectArg::New(alloc(), |
| current->argumentsObject(), |
| GET_ARGNO(pc)); |
| current->add(getArg); |
| current->push(getArg); |
| } else { |
| current->pushArg(GET_ARGNO(pc)); |
| } |
| return true; |
| |
| case JSOP_SETARG: |
| return jsop_setarg(GET_ARGNO(pc)); |
| |
| case JSOP_GETLOCAL: |
| current->pushLocal(GET_LOCALNO(pc)); |
| return true; |
| |
| case JSOP_SETLOCAL: |
| current->setLocal(GET_LOCALNO(pc)); |
| return true; |
| |
| case JSOP_THROWSETCONST: |
| case JSOP_THROWSETALIASEDCONST: |
| return jsop_throwsetconst(); |
| |
| case JSOP_CHECKLEXICAL: |
| return jsop_checklexical(); |
| |
| case JSOP_INITLEXICAL: |
| current->setLocal(GET_LOCALNO(pc)); |
| return true; |
| |
| case JSOP_INITGLEXICAL: { |
| MOZ_ASSERT(!script()->hasNonSyntacticScope()); |
| MDefinition* value = current->pop(); |
| current->push(constant(ObjectValue(script()->global().lexicalScope()))); |
| current->push(value); |
| return jsop_setprop(info().getAtom(pc)->asPropertyName()); |
| } |
| |
| case JSOP_CHECKALIASEDLEXICAL: |
| return jsop_checkaliasedlet(ScopeCoordinate(pc)); |
| |
| case JSOP_INITALIASEDLEXICAL: |
| return jsop_setaliasedvar(ScopeCoordinate(pc)); |
| |
| case JSOP_UNINITIALIZED: |
| return pushConstant(MagicValue(JS_UNINITIALIZED_LEXICAL)); |
| |
| case JSOP_POP: |
| current->pop(); |
| |
| // POP opcodes frequently appear where values are killed, e.g. after |
| // SET* opcodes. Place a resume point afterwards to avoid capturing |
| // the dead value in later snapshots, except in places where that |
| // resume point is obviously unnecessary. |
| if (pc[JSOP_POP_LENGTH] == JSOP_POP) |
| return true; |
| return maybeInsertResume(); |
| |
| case JSOP_POPN: |
| for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++) |
| current->pop(); |
| return true; |
| |
| case JSOP_DUPAT: |
| current->pushSlot(current->stackDepth() - 1 - GET_UINT24(pc)); |
| return true; |
| |
| case JSOP_NEWINIT: |
| if (GET_UINT8(pc) == JSProto_Array) |
| return jsop_newarray(0); |
| return jsop_newobject(); |
| |
| case JSOP_NEWARRAY: |
| return jsop_newarray(GET_UINT32(pc)); |
| |
| case JSOP_NEWARRAY_COPYONWRITE: |
| return jsop_newarray_copyonwrite(); |
| |
| case JSOP_NEWOBJECT: |
| return jsop_newobject(); |
| |
| case JSOP_INITELEM: |
| case JSOP_INITHIDDENELEM: |
| return jsop_initelem(); |
| |
| case JSOP_INITELEM_ARRAY: |
| return jsop_initelem_array(); |
| |
| case JSOP_INITPROP: |
| case JSOP_INITLOCKEDPROP: |
| case JSOP_INITHIDDENPROP: |
| { |
| PropertyName* name = info().getAtom(pc)->asPropertyName(); |
| return jsop_initprop(name); |
| } |
| |
| case JSOP_MUTATEPROTO: |
| { |
| return jsop_mutateproto(); |
| } |
| |
| case JSOP_INITPROP_GETTER: |
| case JSOP_INITHIDDENPROP_GETTER: |
| case JSOP_INITPROP_SETTER: |
| case JSOP_INITHIDDENPROP_SETTER: { |
| PropertyName* name = info().getAtom(pc)->asPropertyName(); |
| return jsop_initprop_getter_setter(name); |
| } |
| |
| case JSOP_INITELEM_GETTER: |
| case JSOP_INITHIDDENELEM_GETTER: |
| case JSOP_INITELEM_SETTER: |
| case JSOP_INITHIDDENELEM_SETTER: |
| return jsop_initelem_getter_setter(); |
| |
| case JSOP_FUNCALL: |
| return jsop_funcall(GET_ARGC(pc)); |
| |
| case JSOP_FUNAPPLY: |
| return jsop_funapply(GET_ARGC(pc)); |
| |
| case JSOP_CALL: |
| case JSOP_CALLITER: |
| case JSOP_NEW: |
| case JSOP_SUPERCALL: |
| return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL); |
| |
| case JSOP_EVAL: |
| case JSOP_STRICTEVAL: |
| return jsop_eval(GET_ARGC(pc)); |
| |
| case JSOP_INT8: |
| return pushConstant(Int32Value(GET_INT8(pc))); |
| |
| case JSOP_UINT16: |
| return pushConstant(Int32Value(GET_UINT16(pc))); |
| |
| case JSOP_GETGNAME: |
| { |
| PropertyName* name = info().getAtom(pc)->asPropertyName(); |
| if (!script()->hasNonSyntacticScope()) |
| return jsop_getgname(name); |
| return jsop_getname(name); |
| } |
| |
| case JSOP_SETGNAME: |
| case JSOP_STRICTSETGNAME: |
| { |
| PropertyName* name = info().getAtom(pc)->asPropertyName(); |
| JSObject* obj = nullptr; |
| if (!script()->hasNonSyntacticScope()) |
| obj = testGlobalLexicalBinding(name); |
| if (obj) |
| return setStaticName(obj, name); |
| return jsop_setprop(name); |
| } |
| |
| case JSOP_GETNAME: |
| { |
| PropertyName* name = info().getAtom(pc)->asPropertyName(); |
| return jsop_getname(name); |
| } |
| |
| case JSOP_GETINTRINSIC: |
| { |
| PropertyName* name = info().getAtom(pc)->asPropertyName(); |
| return jsop_intrinsic(name); |
| } |
| |
| case JSOP_GETIMPORT: |
| { |
| PropertyName* name = info().getAtom(pc)->asPropertyName(); |
| return jsop_getimport(name); |
| } |
| |
| case JSOP_BINDGNAME: |
| if (!script()->hasNonSyntacticScope()) { |
| if (JSObject* scope = testGlobalLexicalBinding(info().getName(pc))) |
| return pushConstant(ObjectValue(*scope)); |
| } |
| // Fall through to JSOP_BINDNAME |
| case JSOP_BINDNAME: |
| return jsop_bindname(info().getName(pc)); |
| |
| case JSOP_DUP: |
| current->pushSlot(current->stackDepth() - 1); |
| return true; |
| |
| case JSOP_DUP2: |
| return jsop_dup2(); |
| |
| case JSOP_SWAP: |
| current->swapAt(-1); |
| return true; |
| |
| case JSOP_PICK: |
| current->pick(-GET_INT8(pc)); |
| return true; |
| |
| case JSOP_GETALIASEDVAR: |
| return jsop_getaliasedvar(ScopeCoordinate(pc)); |
| |
| case JSOP_SETALIASEDVAR: |
| return jsop_setaliasedvar(ScopeCoordinate(pc)); |
| |
| case JSOP_UINT24: |
| return pushConstant(Int32Value(GET_UINT24(pc))); |
| |
| case JSOP_INT32: |
| return pushConstant(Int32Value(GET_INT32(pc))); |
| |
| case JSOP_LOOPHEAD: |
| // JSOP_LOOPHEAD is handled when processing the loop header. |
| MOZ_CRASH("JSOP_LOOPHEAD outside loop"); |
| |
| case JSOP_GETELEM: |
| case JSOP_CALLELEM: |
| if (!jsop_getelem()) |
| return false; |
| if (op == JSOP_CALLELEM && !improveThisTypesForCall()) |
| return false; |
| return true; |
| |
| case JSOP_SETELEM: |
| case JSOP_STRICTSETELEM: |
| return jsop_setelem(); |
| |
| case JSOP_LENGTH: |
| return jsop_length(); |
| |
| case JSOP_NOT: |
| return jsop_not(); |
| |
| case JSOP_FUNCTIONTHIS: |
| return jsop_functionthis(); |
| |
| case JSOP_GLOBALTHIS: |
| return jsop_globalthis(); |
| |
| case JSOP_CALLEE: { |
| MDefinition* callee = getCallee(); |
| current->push(callee); |
| return true; |
| } |
| |
| case JSOP_GETPROP: |
| case JSOP_CALLPROP: |
| { |
| PropertyName* name = info().getAtom(pc)->asPropertyName(); |
| if (!jsop_getprop(name)) |
| return false; |
| if (op == JSOP_CALLPROP && !improveThisTypesForCall()) |
| return false; |
| return true; |
| } |
| |
| case JSOP_SETPROP: |
| case JSOP_STRICTSETPROP: |
| case JSOP_SETNAME: |
| case JSOP_STRICTSETNAME: |
| { |
| PropertyName* name = info().getAtom(pc)->asPropertyName(); |
| return jsop_setprop(name); |
| } |
| |
| case JSOP_DELPROP: |
| case JSOP_STRICTDELPROP: |
| { |
| PropertyName* name = info().getAtom(pc)->asPropertyName(); |
| return jsop_delprop(name); |
| } |
| |
| case JSOP_DELELEM: |
| case JSOP_STRICTDELELEM: |
| return jsop_delelem(); |
| |
| case JSOP_REGEXP: |
| return jsop_regexp(info().getRegExp(pc)); |
| |
| case JSOP_CALLSITEOBJ: |
| return pushConstant(ObjectValue(*(info().getObject(pc)))); |
| |
| case JSOP_OBJECT: |
| return jsop_object(info().getObject(pc)); |
| |
| case JSOP_TYPEOF: |
| case JSOP_TYPEOFEXPR: |
| return jsop_typeof(); |
| |
| case JSOP_TOID: |
| return jsop_toid(); |
| |
| case JSOP_LAMBDA: |
| return jsop_lambda(info().getFunction(pc)); |
| |
| case JSOP_LAMBDA_ARROW: |
| return jsop_lambda_arrow(info().getFunction(pc)); |
| |
| case JSOP_ITER: |
| return jsop_iter(GET_INT8(pc)); |
| |
| case JSOP_MOREITER: |
| return jsop_itermore(); |
| |
| case JSOP_ISNOITER: |
| return jsop_isnoiter(); |
| |
| case JSOP_ENDITER: |
| return jsop_iterend(); |
| |
| case JSOP_IN: |
| return jsop_in(); |
| |
| case JSOP_SETRVAL: |
| MOZ_ASSERT(!script()->noScriptRval()); |
| current->setSlot(info().returnValueSlot(), current->pop()); |
| return true; |
| |
| case JSOP_INSTANCEOF: |
| return jsop_instanceof(); |
| |
| case JSOP_DEBUGLEAVEBLOCK: |
| return true; |
| |
| case JSOP_DEBUGGER: |
| return jsop_debugger(); |
| |
| case JSOP_GIMPLICITTHIS: |
| if (!script()->hasNonSyntacticScope()) |
| return pushConstant(UndefinedValue()); |
| |
| // Just fall through to the unsupported bytecode case. |
| break; |
| |
| case JSOP_NEWTARGET: |
| return jsop_newtarget(); |
| |
| case JSOP_CHECKOBJCOERCIBLE: |
| return jsop_checkobjcoercible(); |
| |
| #ifdef DEBUG |
| case JSOP_PUSHBLOCKSCOPE: |
| case JSOP_FRESHENBLOCKSCOPE: |
| case JSOP_POPBLOCKSCOPE: |
| // These opcodes are currently unhandled by Ion, but in principle |
| // there's no reason they couldn't be. Whenever this happens, OSR will |
| // have to consider that JSOP_FRESHENBLOCK mutates the scope chain -- |
| // right now it caches the scope chain in MBasicBlock::scopeChain(). |
| // That stale value will have to be updated when JSOP_FRESHENBLOCK is |
| // encountered. |
| #endif |
| default: |
| break; |
| } |
| |
| // Track a simpler message, since the actionable abort message is a |
| // static string, and the internal opcode name isn't an actionable |
| // thing anyways. |
| trackActionableAbort("Unsupported bytecode"); |
| #ifdef DEBUG |
| return abort("Unsupported opcode: %s", CodeName[op]); |
| #else |
| return abort("Unsupported opcode: %d", op); |
| #endif |
| } |
| |
| // Given that the current control flow structure has ended forcefully, |
| // via a return, break, or continue (rather than joining), propagate the |
| // termination up. For example, a return nested 5 loops deep may terminate |
| // every outer loop at once, if there are no intervening conditionals: |
| // |
| // for (...) { |
| // for (...) { |
| // return x; |
| // } |
| // } |
| // |
| // If |current| is nullptr when this function returns, then there is no more |
| // control flow to be processed. |
| IonBuilder::ControlStatus |
| IonBuilder::processControlEnd() |
| { |
| MOZ_ASSERT(!current); |
| |
| if (cfgStack_.empty()) { |
| // If there is no more control flow to process, then this is the |
| // last return in the function. |
| return ControlStatus_Ended; |
| } |
| |
| return processCfgStack(); |
| } |
| |
| // Processes the top of the CFG stack. This is used from two places: |
| // (1) processControlEnd(), whereby a break, continue, or return may interrupt |
| // an in-progress CFG structure before reaching its actual termination |
| // point in the bytecode. |
| // (2) traverseBytecode(), whereby we reach the last instruction in a CFG |
| // structure. |
| IonBuilder::ControlStatus |
| IonBuilder::processCfgStack() |
| { |
| ControlStatus status = processCfgEntry(cfgStack_.back()); |
| |
| // If this terminated a CFG structure, act like processControlEnd() and |
| // keep propagating upward. |
| while (status == ControlStatus_Ended) { |
| popCfgStack(); |
| if (cfgStack_.empty()) |
| return status; |
| status = processCfgEntry(cfgStack_.back()); |
| } |
| |
| // If some join took place, the current structure is finished. |
| if (status == ControlStatus_Joined) |
| popCfgStack(); |
| |
| return status; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::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"); |
| } |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processIfEnd(CFGState& state) |
| { |
| bool thenBranchTerminated = !current; |
| if (!thenBranchTerminated) { |
| // Here, the false block is the join point. Create an edge from the |
| // current block to the false block. Note that a RETURN opcode |
| // could have already ended the block. |
| current->end(MGoto::New(alloc(), state.branch.ifFalse)); |
| |
| if (!state.branch.ifFalse->addPredecessor(alloc(), current)) |
| return ControlStatus_Error; |
| } |
| |
| if (!setCurrentAndSpecializePhis(state.branch.ifFalse)) |
| return ControlStatus_Error; |
| graph().moveBlockToEnd(current); |
| pc = current->pc(); |
| |
| if (thenBranchTerminated) { |
| // If we can't reach here via the then-branch, we can filter the types |
| // after the if-statement based on the if-condition. |
| MTest* test = state.branch.test; |
| if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test)) |
| return ControlStatus_Error; |
| } |
| |
| return ControlStatus_Joined; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processIfElseTrueEnd(CFGState& state) |
| { |
| // We've reached the end of the true branch of an if-else. Don't |
| // create an edge yet, just transition to parsing the false branch. |
| state.state = CFGState::IF_ELSE_FALSE; |
| state.branch.ifTrue = current; |
| state.stopAt = state.branch.falseEnd; |
| pc = state.branch.ifFalse->pc(); |
| if (!setCurrentAndSpecializePhis(state.branch.ifFalse)) |
| return ControlStatus_Error; |
| graph().moveBlockToEnd(current); |
| |
| MTest* test = state.branch.test; |
| if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test)) |
| return ControlStatus_Error; |
| |
| return ControlStatus_Jumped; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processIfElseFalseEnd(CFGState& state) |
| { |
| // Update the state to have the latest block from the false path. |
| state.branch.ifFalse = current; |
| |
| // To create the join node, we need an incoming edge that has not been |
| // terminated yet. |
| MBasicBlock* pred = state.branch.ifTrue |
| ? state.branch.ifTrue |
| : state.branch.ifFalse; |
| MBasicBlock* other = (pred == state.branch.ifTrue) ? state.branch.ifFalse : state.branch.ifTrue; |
| |
| if (!pred) |
| return ControlStatus_Ended; |
| |
| // Create a new block to represent the join. |
| MBasicBlock* join = newBlock(pred, state.branch.falseEnd); |
| if (!join) |
| return ControlStatus_Error; |
| |
| // Create edges from the true and false blocks as needed. |
| pred->end(MGoto::New(alloc(), join)); |
| |
| if (other) { |
| other->end(MGoto::New(alloc(), join)); |
| if (!join->addPredecessor(alloc(), other)) |
| return ControlStatus_Error; |
| } |
| |
| // Ignore unreachable remainder of false block if existent. |
| if (!setCurrentAndSpecializePhis(join)) |
| return ControlStatus_Error; |
| pc = current->pc(); |
| return ControlStatus_Joined; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processBrokenLoop(CFGState& state) |
| { |
| MOZ_ASSERT(!current); |
| |
| MOZ_ASSERT(loopDepth_); |
| loopDepth_--; |
| |
| // A broken loop is not a real loop (it has no header or backedge), so |
| // reset the loop depth. |
| for (MBasicBlockIterator i(graph().begin(state.loop.entry)); i != graph().end(); i++) { |
| if (i->loopDepth() > loopDepth_) |
| i->setLoopDepth(i->loopDepth() - 1); |
| } |
| |
| // If the loop started with a condition (while/for) then even if the |
| // structure never actually loops, the condition itself can still fail and |
| // thus we must resume at the successor, if one exists. |
| if (!setCurrentAndSpecializePhis(state.loop.successor)) |
| return ControlStatus_Error; |
| if (current) { |
| MOZ_ASSERT(current->loopDepth() == loopDepth_); |
| graph().moveBlockToEnd(current); |
| } |
| |
| // Join the breaks together and continue parsing. |
| if (state.loop.breaks) { |
| MBasicBlock* block = createBreakCatchBlock(state.loop.breaks, state.loop.exitpc); |
| if (!block) |
| return ControlStatus_Error; |
| |
| if (current) { |
| current->end(MGoto::New(alloc(), block)); |
| if (!block->addPredecessor(alloc(), current)) |
| return ControlStatus_Error; |
| } |
| |
| if (!setCurrentAndSpecializePhis(block)) |
| return ControlStatus_Error; |
| } |
| |
| // If the loop is not gated on a condition, and has only returns, we'll |
| // reach this case. For example: |
| // do { ... return; } while (); |
| if (!current) |
| return ControlStatus_Ended; |
| |
| // Otherwise, the loop is gated on a condition and/or has breaks so keep |
| // parsing at the successor. |
| pc = current->pc(); |
| return ControlStatus_Joined; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::finishLoop(CFGState& state, MBasicBlock* successor) |
| { |
| MOZ_ASSERT(current); |
| |
| MOZ_ASSERT(loopDepth_); |
| loopDepth_--; |
| MOZ_ASSERT_IF(successor, successor->loopDepth() == loopDepth_); |
| |
| // Compute phis in the loop header and propagate them throughout the loop, |
| // including the successor. |
| AbortReason r = state.loop.entry->setBackedge(current); |
| if (r == AbortReason_Alloc) |
| return ControlStatus_Error; |
| if (r == AbortReason_Disable) { |
| // If there are types for variables on the backedge that were not |
| // present at the original loop header, then uses of the variables' |
| // phis may have generated incorrect nodes. The new types have been |
| // incorporated into the header phis, so remove all blocks for the |
| // loop body and restart with the new types. |
| return restartLoop(state); |
| } |
| |
| if (successor) { |
| graph().moveBlockToEnd(successor); |
| successor->inheritPhis(state.loop.entry); |
| } |
| |
| if (state.loop.breaks) { |
| // Propagate phis placed in the header to individual break exit points. |
| DeferredEdge* edge = state.loop.breaks; |
| while (edge) { |
| edge->block->inheritPhis(state.loop.entry); |
| edge = edge->next; |
| } |
| |
| // Create a catch block to join all break exits. |
| MBasicBlock* block = createBreakCatchBlock(state.loop.breaks, state.loop.exitpc); |
| if (!block) |
| return ControlStatus_Error; |
| |
| if (successor) { |
| // Finally, create an unconditional edge from the successor to the |
| // catch block. |
| successor->end(MGoto::New(alloc(), block)); |
| if (!block->addPredecessor(alloc(), successor)) |
| return ControlStatus_Error; |
| } |
| successor = block; |
| } |
| |
| if (!setCurrentAndSpecializePhis(successor)) |
| return ControlStatus_Error; |
| |
| // An infinite loop (for (;;) { }) will not have a successor. |
| if (!current) |
| return ControlStatus_Ended; |
| |
| pc = current->pc(); |
| return ControlStatus_Joined; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::restartLoop(CFGState state) |
| { |
| spew("New types at loop header, restarting loop body"); |
| |
| if (JitOptions.limitScriptSize) { |
| if (++numLoopRestarts_ >= MAX_LOOP_RESTARTS) |
| return ControlStatus_Abort; |
| } |
| |
| MBasicBlock* header = state.loop.entry; |
| |
| // Discard unreferenced & pre-allocated resume points. |
| replaceMaybeFallbackFunctionGetter(nullptr); |
| |
| // Remove all blocks in the loop body other than the header, which has phis |
| // of the appropriate type and incoming edges to preserve. |
| graph().removeBlocksAfter(header); |
| |
| // Remove all instructions from the header itself, and all resume points |
| // except the entry resume point. |
| header->discardAllInstructions(); |
| header->discardAllResumePoints(/* discardEntry = */ false); |
| header->setStackDepth(header->getPredecessor(0)->stackDepth()); |
| |
| popCfgStack(); |
| |
| loopDepth_++; |
| |
| if (!pushLoop(state.loop.initialState, state.loop.initialStopAt, header, state.loop.osr, |
| state.loop.loopHead, state.loop.initialPc, |
| state.loop.bodyStart, state.loop.bodyEnd, |
| state.loop.exitpc, state.loop.continuepc)) |
| { |
| return ControlStatus_Error; |
| } |
| |
| CFGState& nstate = cfgStack_.back(); |
| |
| nstate.loop.condpc = state.loop.condpc; |
| nstate.loop.updatepc = state.loop.updatepc; |
| nstate.loop.updateEnd = state.loop.updateEnd; |
| |
| // Don't specializePhis(), as the header has been visited before and the |
| // phis have already had their type set. |
| setCurrent(header); |
| |
| if (!jsop_loophead(nstate.loop.loopHead)) |
| return ControlStatus_Error; |
| |
| pc = nstate.loop.initialPc; |
| return ControlStatus_Jumped; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processDoWhileBodyEnd(CFGState& state) |
| { |
| if (!processDeferredContinues(state)) |
| return ControlStatus_Error; |
| |
| // No current means control flow cannot reach the condition, so this will |
| // never loop. |
| if (!current) |
| return processBrokenLoop(state); |
| |
| MBasicBlock* header = newBlock(current, state.loop.updatepc); |
| if (!header) |
| return ControlStatus_Error; |
| current->end(MGoto::New(alloc(), header)); |
| |
| state.state = CFGState::DO_WHILE_LOOP_COND; |
| state.stopAt = state.loop.updateEnd; |
| pc = state.loop.updatepc; |
| if (!setCurrentAndSpecializePhis(header)) |
| return ControlStatus_Error; |
| return ControlStatus_Jumped; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processDoWhileCondEnd(CFGState& state) |
| { |
| MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE); |
| |
| // We're guaranteed a |current|, it's impossible to break or return from |
| // inside the conditional expression. |
| MOZ_ASSERT(current); |
| |
| // Pop the last value, and create the successor block. |
| MDefinition* vins = current->pop(); |
| MBasicBlock* successor = newBlock(current, GetNextPc(pc), loopDepth_ - 1); |
| if (!successor) |
| return ControlStatus_Error; |
| |
| // Test for do {} while(false) and don't create a loop in that case. |
| if (vins->isConstantValue() && !vins->constantValue().isMagic()) { |
| if (!vins->constantToBoolean()) { |
| current->end(MGoto::New(alloc(), successor)); |
| current = nullptr; |
| |
| state.loop.successor = successor; |
| return processBrokenLoop(state); |
| } |
| } |
| |
| // Create the test instruction and end the current block. |
| MTest* test = newTest(vins, state.loop.entry, successor); |
| current->end(test); |
| return finishLoop(state, successor); |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processWhileCondEnd(CFGState& state) |
| { |
| MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE || JSOp(*pc) == JSOP_IFEQ); |
| |
| // Balance the stack past the IFNE. |
| MDefinition* ins = current->pop(); |
| |
| // Create the body and successor blocks. |
| MBasicBlock* body = newBlock(current, state.loop.bodyStart); |
| state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1); |
| if (!body || !state.loop.successor) |
| return ControlStatus_Error; |
| |
| MTest* test; |
| if (JSOp(*pc) == JSOP_IFNE) |
| test = newTest(ins, body, state.loop.successor); |
| else |
| test = newTest(ins, state.loop.successor, body); |
| current->end(test); |
| |
| state.state = CFGState::WHILE_LOOP_BODY; |
| state.stopAt = state.loop.bodyEnd; |
| pc = state.loop.bodyStart; |
| if (!setCurrentAndSpecializePhis(body)) |
| return ControlStatus_Error; |
| |
| // Filter the types in the loop body. |
| if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test)) |
| return ControlStatus_Error; |
| |
| // If this is a for-in loop, unbox the current value as string if possible. |
| if (ins->isIsNoIter()) { |
| MIteratorMore* iterMore = ins->toIsNoIter()->input()->toIteratorMore(); |
| jsbytecode* iterMorePc = iterMore->resumePoint()->pc(); |
| MOZ_ASSERT(*iterMorePc == JSOP_MOREITER); |
| |
| if (!nonStringIteration_ && !inspector->hasSeenNonStringIterMore(iterMorePc)) { |
| MDefinition* val = current->peek(-1); |
| MOZ_ASSERT(val == iterMore); |
| MInstruction* ins = MUnbox::New(alloc(), val, MIRType_String, MUnbox::Fallible, |
| Bailout_NonStringInputInvalidate); |
| current->add(ins); |
| current->rewriteAtDepth(-1, ins); |
| } |
| } |
| |
| return ControlStatus_Jumped; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processWhileBodyEnd(CFGState& state) |
| { |
| if (!processDeferredContinues(state)) |
| return ControlStatus_Error; |
| |
| if (!current) |
| return processBrokenLoop(state); |
| |
| current->end(MGoto::New(alloc(), state.loop.entry)); |
| return finishLoop(state, state.loop.successor); |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processForCondEnd(CFGState& state) |
| { |
| MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE); |
| |
| // Balance the stack past the IFNE. |
| MDefinition* ins = current->pop(); |
| |
| // Create the body and successor blocks. |
| MBasicBlock* body = newBlock(current, state.loop.bodyStart); |
| state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1); |
| if (!body || !state.loop.successor) |
| return ControlStatus_Error; |
| |
| MTest* test = newTest(ins, body, state.loop.successor); |
| current->end(test); |
| |
| state.state = CFGState::FOR_LOOP_BODY; |
| state.stopAt = state.loop.bodyEnd; |
| pc = state.loop.bodyStart; |
| if (!setCurrentAndSpecializePhis(body)) |
| return ControlStatus_Error; |
| return ControlStatus_Jumped; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processForBodyEnd(CFGState& state) |
| { |
| if (!processDeferredContinues(state)) |
| return ControlStatus_Error; |
| |
| // If there is no updatepc, just go right to processing what would be the |
| // end of the update clause. Otherwise, |current| might be nullptr; if this is |
| // the case, the udpate is unreachable anyway. |
| if (!state.loop.updatepc || !current) |
| return processForUpdateEnd(state); |
| |
| pc = state.loop.updatepc; |
| |
| state.state = CFGState::FOR_LOOP_UPDATE; |
| state.stopAt = state.loop.updateEnd; |
| return ControlStatus_Jumped; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processForUpdateEnd(CFGState& state) |
| { |
| // If there is no current, we couldn't reach the loop edge and there was no |
| // update clause. |
| if (!current) |
| return processBrokenLoop(state); |
| |
| current->end(MGoto::New(alloc(), state.loop.entry)); |
| return finishLoop(state, state.loop.successor); |
| } |
| |
| IonBuilder::DeferredEdge* |
| IonBuilder::filterDeadDeferredEdges(DeferredEdge* edge) |
| { |
| DeferredEdge* head = edge; |
| DeferredEdge* prev = nullptr; |
| |
| while (edge) { |
| if (edge->block->isDead()) { |
| if (prev) |
| prev->next = edge->next; |
| else |
| head = edge->next; |
| } else { |
| prev = edge; |
| } |
| edge = edge->next; |
| } |
| |
| // There must be at least one deferred edge from a block that was not |
| // deleted; blocks are deleted when restarting processing of a loop, and |
| // the final version of the loop body will have edges from live blocks. |
| MOZ_ASSERT(head); |
| |
| return head; |
| } |
| |
| bool |
| IonBuilder::processDeferredContinues(CFGState& state) |
| { |
| // If there are any continues for this loop, and there is an update block, |
| // then we need to create a new basic block to house the update. |
| if (state.loop.continues) { |
| DeferredEdge* edge = filterDeadDeferredEdges(state.loop.continues); |
| |
| MBasicBlock* update = newBlock(edge->block, loops_.back().continuepc); |
| if (!update) |
| return false; |
| |
| if (current) { |
| current->end(MGoto::New(alloc(), update)); |
| if (!update->addPredecessor(alloc(), current)) |
| return false; |
| } |
| |
| // No need to use addPredecessor for first edge, |
| // because it is already predecessor. |
| edge->block->end(MGoto::New(alloc(), update)); |
| edge = edge->next; |
| |
| // Remaining edges |
| while (edge) { |
| edge->block->end(MGoto::New(alloc(), update)); |
| if (!update->addPredecessor(alloc(), edge->block)) |
| return false; |
| edge = edge->next; |
| } |
| state.loop.continues = nullptr; |
| |
| if (!setCurrentAndSpecializePhis(update)) |
| return ControlStatus_Error; |
| } |
| |
| return true; |
| } |
| |
| MBasicBlock* |
| IonBuilder::createBreakCatchBlock(DeferredEdge* edge, jsbytecode* pc) |
| { |
| edge = filterDeadDeferredEdges(edge); |
| |
| // Create block, using the first break statement as predecessor |
| MBasicBlock* successor = newBlock(edge->block, pc); |
| if (!successor) |
| return nullptr; |
| |
| // No need to use addPredecessor for first edge, |
| // because it is already predecessor. |
| edge->block->end(MGoto::New(alloc(), successor)); |
| edge = edge->next; |
| |
| // Finish up remaining breaks. |
| while (edge) { |
| edge->block->end(MGoto::New(alloc(), successor)); |
| if (!successor->addPredecessor(alloc(), edge->block)) |
| return nullptr; |
| edge = edge->next; |
| } |
| |
| return successor; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processNextTableSwitchCase(CFGState& state) |
| { |
| MOZ_ASSERT(state.state == CFGState::TABLE_SWITCH); |
| |
| state.tableswitch.currentBlock++; |
| |
| // Test if there are still unprocessed successors (cases/default) |
| if (state.tableswitch.currentBlock >= state.tableswitch.ins->numBlocks()) |
| return processSwitchEnd(state.tableswitch.breaks, state.tableswitch.exitpc); |
| |
| // Get the next successor |
| MBasicBlock* successor = state.tableswitch.ins->getBlock(state.tableswitch.currentBlock); |
| |
| // Add current block as predecessor if available. |
| // This means the previous case didn't have a break statement. |
| // So flow will continue in this block. |
| if (current) { |
| current->end(MGoto::New(alloc(), successor)); |
| if (!successor->addPredecessor(alloc(), current)) |
| return ControlStatus_Error; |
| } else { |
| // If this is an actual case statement, optimize by replacing the |
| // input to the switch case with the actual number of the case. |
| // This constant has been emitted when creating the case blocks. |
| if (state.tableswitch.ins->getDefault() != successor) { |
| MConstant* constant = successor->begin()->toConstant(); |
| for (uint32_t j = 0; j < successor->stackDepth(); j++) { |
| MDefinition* ins = successor->getSlot(j); |
| if (ins != state.tableswitch.ins->getOperand(0)) |
| continue; |
| |
| constant->setDependency(state.tableswitch.ins); |
| successor->setSlot(j, constant); |
| } |
| } |
| } |
| |
| // Insert successor after the current block, to maintain RPO. |
| graph().moveBlockToEnd(successor); |
| |
| // If this is the last successor the block should stop at the end of the tableswitch |
| // Else it should stop at the start of the next successor |
| if (state.tableswitch.currentBlock+1 < state.tableswitch.ins->numBlocks()) |
| state.stopAt = state.tableswitch.ins->getBlock(state.tableswitch.currentBlock+1)->pc(); |
| else |
| state.stopAt = state.tableswitch.exitpc; |
| |
| if (!setCurrentAndSpecializePhis(successor)) |
| return ControlStatus_Error; |
| pc = current->pc(); |
| return ControlStatus_Jumped; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processAndOrEnd(CFGState& state) |
| { |
| MOZ_ASSERT(current); |
| MBasicBlock* lhs = state.branch.ifFalse; |
| |
| // Create a new block to represent the join. |
| MBasicBlock* join = newBlock(current, state.stopAt); |
| if (!join) |
| return ControlStatus_Error; |
| |
| // End the rhs. |
| current->end(MGoto::New(alloc(), join)); |
| |
| // End the lhs. |
| lhs->end(MGoto::New(alloc(), join)); |
| if (!join->addPredecessor(alloc(), state.branch.ifFalse)) |
| return ControlStatus_Error; |
| |
| // Set the join path as current path. |
| if (!setCurrentAndSpecializePhis(join)) |
| return ControlStatus_Error; |
| pc = current->pc(); |
| return ControlStatus_Joined; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processLabelEnd(CFGState& state) |
| { |
| MOZ_ASSERT(state.state == CFGState::LABEL); |
| |
| // If there are no breaks and no current, controlflow is terminated. |
| if (!state.label.breaks && !current) |
| return ControlStatus_Ended; |
| |
| // If there are no breaks to this label, there's nothing to do. |
| if (!state.label.breaks) |
| return ControlStatus_Joined; |
| |
| MBasicBlock* successor = createBreakCatchBlock(state.label.breaks, state.stopAt); |
| if (!successor) |
| return ControlStatus_Error; |
| |
| if (current) { |
| current->end(MGoto::New(alloc(), successor)); |
| if (!successor->addPredecessor(alloc(), current)) |
| return ControlStatus_Error; |
| } |
| |
| pc = state.stopAt; |
| if (!setCurrentAndSpecializePhis(successor)) |
| return ControlStatus_Error; |
| return ControlStatus_Joined; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processTryEnd(CFGState& state) |
| { |
| MOZ_ASSERT(state.state == CFGState::TRY); |
| |
| if (!state.try_.successor) { |
| MOZ_ASSERT(!current); |
| return ControlStatus_Ended; |
| } |
| |
| if (current) { |
| current->end(MGoto::New(alloc(), state.try_.successor)); |
| |
| if (!state.try_.successor->addPredecessor(alloc(), current)) |
| return ControlStatus_Error; |
| } |
| |
| // Start parsing the code after this try-catch statement. |
| if (!setCurrentAndSpecializePhis(state.try_.successor)) |
| return ControlStatus_Error; |
| graph().moveBlockToEnd(current); |
| pc = current->pc(); |
| return ControlStatus_Joined; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processBreak(JSOp op, jssrcnote* sn) |
| { |
| MOZ_ASSERT(op == JSOP_GOTO); |
| |
| MOZ_ASSERT(SN_TYPE(sn) == SRC_BREAK || |
| SN_TYPE(sn) == SRC_BREAK2LABEL); |
| |
| // Find the break target. |
| jsbytecode* target = pc + GetJumpOffset(pc); |
| DebugOnly<bool> found = false; |
| |
| if (SN_TYPE(sn) == SRC_BREAK2LABEL) { |
| for (size_t i = labels_.length() - 1; i < labels_.length(); 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; |
| } |
| } |
| } else { |
| for (size_t i = loops_.length() - 1; i < loops_.length(); 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; |
| } |
| } |
| } |
| |
| MOZ_ASSERT(found); |
| |
| setCurrent(nullptr); |
| pc += CodeSpec[op].length; |
| return processControlEnd(); |
| } |
| |
| static inline jsbytecode* |
| EffectiveContinue(jsbytecode* pc) |
| { |
| if (JSOp(*pc) == JSOP_GOTO) |
| return pc + GetJumpOffset(pc); |
| return pc; |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processContinue(JSOp op) |
| { |
| MOZ_ASSERT(op == JSOP_GOTO); |
| |
| // Find the target loop. |
| CFGState* found = nullptr; |
| jsbytecode* target = pc + GetJumpOffset(pc); |
| for (size_t i = loops_.length() - 1; i < loops_.length(); i--) { |
| if (loops_[i].continuepc == target || |
| EffectiveContinue(loops_[i].continuepc) == target) |
| { |
| found = &cfgStack_[loops_[i].cfgEntry]; |
| break; |
| } |
| } |
| |
| // There must always be a valid target loop structure. If not, there's |
| // probably an off-by-something error in which pc we track. |
| MOZ_ASSERT(found); |
| CFGState& state = *found; |
| |
| state.loop.continues = new(alloc()) DeferredEdge(current, state.loop.continues); |
| |
| setCurrent(nullptr); |
| pc += CodeSpec[op].length; |
| return processControlEnd(); |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processSwitchBreak(JSOp op) |
| { |
| MOZ_ASSERT(op == JSOP_GOTO); |
| |
| // Find the target switch. |
| CFGState* found = nullptr; |
| jsbytecode* target = pc + GetJumpOffset(pc); |
| for (size_t i = switches_.length() - 1; i < switches_.length(); i--) { |
| if (switches_[i].continuepc == target) { |
| found = &cfgStack_[switches_[i].cfgEntry]; |
| break; |
| } |
| } |
| |
| // There must always be a valid target loop structure. If not, there's |
| // probably an off-by-something error in which pc we track. |
| MOZ_ASSERT(found); |
| CFGState& state = *found; |
| |
| DeferredEdge** breaks = nullptr; |
| switch (state.state) { |
| case CFGState::TABLE_SWITCH: |
| breaks = &state.tableswitch.breaks; |
| break; |
| case CFGState::COND_SWITCH_BODY: |
| breaks = &state.condswitch.breaks; |
| break; |
| default: |
| MOZ_CRASH("Unexpected switch state."); |
| } |
| |
| *breaks = new(alloc()) DeferredEdge(current, *breaks); |
| |
| setCurrent(nullptr); |
| pc += CodeSpec[op].length; |
| return processControlEnd(); |
| } |
| |
| IonBuilder::ControlStatus |
| IonBuilder::processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc) |
| { |
| // No break statements, no current. |
| // This means that control flow is cut-off from this point |
| // (e.g. all cases have return statements). |
| if (!breaks && !current) |
| return ControlStatus_Ended; |
| |
| // Create successor block. |
| // If there are breaks, create block with breaks as predecessor |
| // Else create a block with current as predecessor |
| MBasicBlock* successor = nullptr; |
| if (breaks) |
| successor = createBreakCatchBlock(breaks, exitpc); |
| else |
| successor = newBlock(current, exitpc); |
| |
| if (!successor) |
| return ControlStatus_Ended; |
| |
| // If there is current, the current block flows into this one. |
| // So current is also a predecessor to this block |
| if (current) { |
| current->end(MGoto::New(alloc(), successor)); |
| if (breaks) { |
| if (!successor->addPredecessor(alloc(), current)) |
| return ControlStatus_Error; |
| } |
| } |
| |
| pc = exitpc; |
| if (!setCurrentAndSpecializePhis(successor)) |
| return ControlStatus_Error; |
| return ControlStatus_Joined; |
| } |
| |
| IonBuilder::
|