diff --git a/js/src/ion/IonBuilder.cpp b/js/src/ion/IonBuilder.cpp index 05a3dba732e0..31a571cae20e 100644 --- a/js/src/ion/IonBuilder.cpp +++ b/js/src/ion/IonBuilder.cpp @@ -51,7 +51,8 @@ IonBuilder::IonBuilder(JSContext *cx, TempAllocator *temp, MIRGraph *graph, failedBoundsCheck_(info->script()->failedBoundsCheck), failedShapeGuard_(info->script()->failedShapeGuard), nonStringIteration_(false), - lazyArguments_(NULL) + lazyArguments_(NULL), + inlineCallInfo_(NULL) { script_.init(info->script()); pc = info->startPC(); @@ -160,9 +161,13 @@ IonBuilder::getSingleCallTarget(types::StackTypeSet *calleeTypes) bool IonBuilder::getPolyCallTargets(types::StackTypeSet *calleeTypes, - AutoObjectVector &targets, uint32_t maxTargets) + AutoObjectVector &targets, + uint32_t maxTargets, + bool *gotLambda) { JS_ASSERT(targets.length() == 0); + JS_ASSERT(gotLambda); + *gotLambda = false; if (!calleeTypes) return true; @@ -179,16 +184,33 @@ IonBuilder::getPolyCallTargets(types::StackTypeSet *calleeTypes, return false; for(unsigned i = 0; i < objCount; i++) { JSObject *obj = calleeTypes->getSingleObject(i); - if (!obj || !obj->isFunction()) { - targets.clear(); - return true; + if (obj) { + if (!obj->isFunction()) { + targets.clear(); + return true; + } + if (obj->toFunction()->isInterpreted() && !obj->toFunction()->getOrCreateScript(cx)) + return false; + DebugOnly appendOk = targets.append(obj); + JS_ASSERT(appendOk); + } else { + types::TypeObject *typeObj = calleeTypes->getTypeObject(i); + JS_ASSERT(typeObj); + if (!typeObj->isFunction() || !typeObj->interpretedFunction) { + targets.clear(); + return true; + } + DebugOnly appendOk = targets.append(typeObj->interpretedFunction); + JS_ASSERT(appendOk); + + *gotLambda = true; } - if (obj->toFunction()->isInterpreted() && !obj->toFunction()->getOrCreateScript(cx)) - return false; - if (!targets.append(obj)) - return false; } + // For now, only inline "singleton" lambda calls + if (*gotLambda && targets.length() > 1) + targets.clear(); + return true; } @@ -209,9 +231,6 @@ IonBuilder::canEnterInlinedFunction(JSFunction *target) if (!targetScript->compileAndGo) return false; - if (targetScript->analysis()->usesScopeChain()) - return false; - types::TypeObject *targetType = target->getType(cx); if (!targetType || targetType->unknownProperties()) return false; @@ -220,7 +239,7 @@ IonBuilder::canEnterInlinedFunction(JSFunction *target) } bool -IonBuilder::canInlineTarget(JSFunction *target, CallInfo &callInfo) +IonBuilder::canInlineTarget(JSFunction *target) { if (!target->isInterpreted()) { IonSpew(IonSpew_Inlining, "Cannot inline due to non-interpreted"); @@ -572,6 +591,8 @@ bool IonBuilder::buildInline(IonBuilder *callerBuilder, MResumePoint *callerResumePoint, CallInfo &callInfo) { + inlineCallInfo_ = &callInfo; + if (!script()->ensureHasBytecodeTypeMap(cx)) return false; @@ -614,23 +635,21 @@ IonBuilder::buildInline(IonBuilder *callerBuilder, MResumePoint *callerResumePoi if (!current->addPredecessorWithoutPhis(predecessor)) return false; - // Save the actual arguments the caller used to call this inlined call, - // to shortcut operations on "arguments" in the inlined call. - JS_ASSERT(inlinedArguments_.length() == 0); - if (!inlinedArguments_.append(callInfo.argv().begin(), callInfo.argv().end())) - return false; + // Initialize scope chain slot to Undefined. It's set later by |initScopeChain|. + { + MInstruction *scope = MConstant::New(UndefinedValue()); + current->add(scope); + current->initSlot(info().scopeChainSlot(), scope); + } - // canEnterInlinedFunction vetoes scripts which use the scope chain or needs an - // arguments object. - JS_ASSERT(!script()->analysis()->usesScopeChain()); - MInstruction *scope = MConstant::New(UndefinedValue()); - current->add(scope); - current->initSlot(info().scopeChainSlot(), scope); + // Initialize |arguments| slot. if (info().hasArguments()) { MInstruction *argsObj = MConstant::New(UndefinedValue()); current->add(argsObj); current->initSlot(info().argsObjSlot(), argsObj); } + + // Initialize |this| slot. current->initSlot(info().thisSlot(), callInfo.thisArg()); IonSpew(IonSpew_Inlining, "Initializing %u arg slots", info().nargs()); @@ -653,6 +672,10 @@ IonBuilder::buildInline(IonBuilder *callerBuilder, MResumePoint *callerResumePoi current->initSlot(info().argSlot(i), arg); } + // Initialize the scope chain now that args are initialized. + if (!initScopeChain(callInfo.fun())) + return false; + IonSpew(IonSpew_Inlining, "Initializing %u local slots", info().nlocals()); // Initialize local variables. @@ -765,7 +788,7 @@ IonBuilder::initParameters() } bool -IonBuilder::initScopeChain() +IonBuilder::initScopeChain(MDefinition *callee) { MInstruction *scope = NULL; @@ -784,9 +807,11 @@ IonBuilder::initScopeChain() return abort("non-CNG global scripts are not supported"); if (JSFunction *fun = info().fun()) { - MCallee *callee = MCallee::New(); - current->add(callee); - + if (!callee) { + MCallee *calleeIns = MCallee::New(); + current->add(calleeIns); + callee = calleeIns; + } scope = MFunctionEnvironment::New(callee); current->add(scope); @@ -1392,12 +1417,14 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_CALLEE: { - MInstruction *callee; - if (inliningDepth_ == 0) - callee = MCallee::New(); - else - callee = MConstant::New(ObjectValue(*info().fun())); - current->add(callee); + MDefinition *callee; + if (inliningDepth_ == 0) { + MInstruction *calleeIns = MCallee::New(); + current->add(calleeIns); + callee = calleeIns; + } else { + callee = inlineCallInfo_->fun(); + } current->push(callee); return true; } @@ -3556,7 +3583,7 @@ IonBuilder::makeInliningDecision(JSFunction *target, CallInfo &callInfo) return true; // Determine whether inlining is possible at callee site - if (!canInlineTarget(target, callInfo)) + if (!canInlineTarget(target)) return false; // Heuristics! @@ -3834,9 +3861,6 @@ IonBuilder::makePolyInlineDispatch(JSContext *cx, CallInfo &callInfo, IonBuilder::InliningStatus IonBuilder::inlineSingleCall(CallInfo &callInfo, JSFunction *target) { - // The inlined target must always be explicitly provided as a constant. - JS_ASSERT(callInfo.fun()->isConstant()); - // Expects formals to be popped and wrapped. if (target->isNative()) return inlineNativeCall(callInfo, target->native()); @@ -3848,7 +3872,7 @@ IonBuilder::inlineSingleCall(CallInfo &callInfo, JSFunction *target) IonBuilder::InliningStatus IonBuilder::inlineCallsite(AutoObjectVector &targets, AutoObjectVector &originals, - CallInfo &callInfo) + bool lambda, CallInfo &callInfo) { if (!inliningEnabled()) return InliningStatus_NotInlined; @@ -3868,11 +3892,20 @@ IonBuilder::inlineCallsite(AutoObjectVector &targets, AutoObjectVector &original if (!makeInliningDecision(target, callInfo)) return InliningStatus_NotInlined; - // Replace the function with an MConstant. + // Inlining will elminate uses of the original callee, but it needs to + // be preserved in phis if we bail out. Mark the old callee definition as + // folded to ensure this happens. callInfo.fun()->setFoldedUnchecked(); - MConstant *constFun = MConstant::New(ObjectValue(*target)); - current->add(constFun); - callInfo.setFun(constFun); + + // If the callee is not going to be a lambda (which may vary across + // different invocations), then the callee definition can be replaced by a + // constant. + if (!lambda) { + // Replace the function with an MConstant. + MConstant *constFun = MConstant::New(ObjectValue(*target)); + current->add(constFun); + callInfo.setFun(constFun); + } return inlineSingleCall(callInfo, target); } @@ -4628,7 +4661,7 @@ IonBuilder::jsop_funapplyarguments(uint32_t argc) // Arguments Vector args(cx); - if (!args.append(inlinedArguments_.begin(), inlinedArguments_.end())) + if (!args.append(inlineCallInfo_->argv().begin(), inlineCallInfo_->argv().end())) return false; callInfo.setArgs(&args); @@ -4679,11 +4712,13 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing) // Acquire known call target if existent. AutoObjectVector originals(cx); + bool gotLambda; types::StackTypeSet *calleeTypes = current->peek(calleeDepth)->resultTypeSet(); if (calleeTypes) { - if (!getPolyCallTargets(calleeTypes, originals, 4)) + if (!getPolyCallTargets(calleeTypes, originals, 4, &gotLambda)) return false; } + JS_ASSERT_IF(gotLambda, originals.length() <= 1); // If any call targets need to be cloned, clone them. Keep track of the // originals as we need to case on them for poly inline. @@ -4707,8 +4742,11 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing) if (!callInfo.init(current, argc)) return false; + if (gotLambda && targets.length() > 0) + callInfo.setLambda(true); + // Try inlining - InliningStatus status = inlineCallsite(targets, originals, callInfo); + InliningStatus status = inlineCallsite(targets, originals, gotLambda, callInfo); if (status == InliningStatus_Inlined) return true; if (status == InliningStatus_Error) @@ -6947,7 +6985,7 @@ IonBuilder::jsop_arguments_length() } // We are inlining and know the number of arguments the callee pushed - return pushConstant(Int32Value(inlinedArguments_.length())); + return pushConstant(Int32Value(inlineCallInfo_->argv().length())); } bool @@ -7010,7 +7048,7 @@ IonBuilder::jsop_rest() } // We know the exact number of arguments the callee pushed. - unsigned numActuals = inlinedArguments_.length(); + unsigned numActuals = inlineCallInfo_->argv().length(); unsigned numFormals = info().nargs() - 1; unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0; JSObject *templateObject = getNewArrayTemplateObject(numRest); @@ -7043,7 +7081,7 @@ IonBuilder::jsop_rest() current->add(index); MInstruction *store; - MDefinition *arg = inlinedArguments_[i]; + MDefinition *arg = inlineCallInfo_->argv()[i]; if (elemTypes && !TypeSetIncludes(elemTypes, arg->type(), arg->resultTypeSet())) { elemTypes->addFreeze(cx); store = MCallSetElement::New(array, index, arg); diff --git a/js/src/ion/IonBuilder.h b/js/src/ion/IonBuilder.h index 2cfb2877f8e2..e1a47e2c01da 100644 --- a/js/src/ion/IonBuilder.h +++ b/js/src/ion/IonBuilder.h @@ -219,8 +219,10 @@ class IonBuilder : public MIRGenerator JSFunction *getSingleCallTarget(types::StackTypeSet *calleeTypes); bool getPolyCallTargets(types::StackTypeSet *calleeTypes, - AutoObjectVector &targets, uint32_t maxTargets); - bool canInlineTarget(JSFunction *target, CallInfo &callInfo); + AutoObjectVector &targets, + uint32_t maxTargets, + bool *gotLambda); + bool canInlineTarget(JSFunction *target); void popCfgStack(); DeferredEdge *filterDeadDeferredEdges(DeferredEdge *edge); @@ -311,7 +313,7 @@ class IonBuilder : public MIRGenerator bool initParameters(); void rewriteParameter(uint32_t slotIdx, MDefinition *param, int32_t argIndex); void rewriteParameters(); - bool initScopeChain(); + bool initScopeChain(MDefinition *callee = NULL); bool initArgumentsObject(); bool pushConstant(const Value &v); @@ -508,7 +510,7 @@ class IonBuilder : public MIRGenerator // Call functions InliningStatus inlineCallsite(AutoObjectVector &targets, AutoObjectVector &originals, - CallInfo &callInfo); + bool lambda, CallInfo &callInfo); bool inlineCalls(CallInfo &callInfo, AutoObjectVector &targets, AutoObjectVector &originals, Vector &choiceSet, MGetPropertyCache *maybeCache); @@ -604,7 +606,6 @@ class IonBuilder : public MIRGenerator BaselineInspector *inspector; size_t inliningDepth_; - Vector inlinedArguments_; // Cutoff to disable compilation if excessive time is spent reanalyzing // loop bodies to compute a fixpoint of the types for loop variables. @@ -625,6 +626,9 @@ class IonBuilder : public MIRGenerator // If this script can use a lazy arguments object, it will be pre-created // here. MInstruction *lazyArguments_; + + // If this is an inline builder, the call info for the builder. + const CallInfo *inlineCallInfo_; }; class CallInfo @@ -634,6 +638,7 @@ class CallInfo Vector args_; bool constructing_; + bool lambda_; public: CallInfo(JSContext *cx, bool constructing) @@ -700,6 +705,10 @@ class CallInfo return args_; } + const Vector &argv() const { + return args_; + } + MDefinition *getArg(uint32_t i) { JS_ASSERT(i < argc()); return args_[i]; @@ -723,6 +732,13 @@ class CallInfo return constructing_; } + bool isLambda() const { + return lambda_; + } + void setLambda(bool lambda) { + lambda_ = lambda; + } + void wrapArgs(MBasicBlock *current) { thisArg_ = wrap(current, thisArg_); for (uint32_t i = 0; i < argc(); i++) diff --git a/js/src/ion/MIR.h b/js/src/ion/MIR.h index 15c1430a06a1..4450915e2a9c 100644 --- a/js/src/ion/MIR.h +++ b/js/src/ion/MIR.h @@ -6180,6 +6180,10 @@ class MFunctionEnvironment MDefinition *function() const { return getOperand(0); } + + TypePolicy *typePolicy() { + return this; + } }; // Loads the current js::ForkJoinSlice*. diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index 10812cef3d86..3a68d431968d 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -280,7 +280,6 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) case JSOP_SETALIASEDVAR: case JSOP_LAMBDA: usesScopeChain_ = true; - isIonInlineable = false; break; case JSOP_DEFFUN: