From 989a835c9038ecd0a2f36cfc8e15aad75745ba24 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Fri, 31 May 2013 16:22:34 -0600 Subject: [PATCH] Bug 864218 - Improve performance when accessing variables defined in run-once closures, r=luke,jandem. --- js/src/frontend/BytecodeEmitter.cpp | 20 ++- js/src/ion/BaselineCompiler.cpp | 40 +++++ js/src/ion/BaselineCompiler.h | 1 + js/src/ion/BaselineIC.cpp | 19 +- js/src/ion/CodeGenerator.cpp | 44 +++-- js/src/ion/CodeGenerator.h | 1 + js/src/ion/IonBuilder.cpp | 250 +++++++++++++++++++-------- js/src/ion/IonBuilder.h | 9 +- js/src/ion/LIR-Common.h | 11 +- js/src/ion/LOpcodes.h | 1 + js/src/ion/Lowering.cpp | 7 + js/src/ion/Lowering.h | 1 + js/src/ion/MIR.h | 30 +++- js/src/ion/MOpcodes.h | 1 + js/src/ion/ParallelArrayAnalysis.cpp | 1 + js/src/ion/VMFunctions.cpp | 18 +- js/src/ion/VMFunctions.h | 5 +- js/src/jsinfer.cpp | 1 + js/src/jsinfer.h | 8 +- js/src/jsinferinlines.h | 2 + js/src/jsinterp.cpp | 36 +++- js/src/jsinterp.h | 3 + js/src/jsopcode.tbl | 7 +- js/src/jsscript.cpp | 2 +- js/src/jsscript.h | 1 + js/src/vm/ArgumentsObject-inl.h | 4 +- js/src/vm/ArgumentsObject.cpp | 4 +- js/src/vm/ArgumentsObject.h | 2 +- js/src/vm/ScopeObject-inl.h | 14 +- js/src/vm/ScopeObject.cpp | 67 +++++-- js/src/vm/ScopeObject.h | 12 +- js/src/vm/Xdr.h | 2 +- 32 files changed, 486 insertions(+), 138 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index b55ebcb2519..4f080ed78c1 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2559,6 +2559,22 @@ frontend::EmitFunctionScript(JSContext *cx, BytecodeEmitter *bce, ParseNode *bod bce->switchToMain(); } + /* + * Emit a prologue for run-once scripts which will deoptimize JIT code if + * the script ends up running multiple times via foo.caller related + * shenanigans. + */ + bool runOnce = bce->parent && + bce->parent->emittingRunOnceLambda && + !funbox->argumentsHasLocalBinding() && + !funbox->isGenerator(); + if (runOnce) { + bce->switchToProlog(); + if (!Emit1(cx, bce, JSOP_RUNONCE) < 0) + return false; + bce->switchToMain(); + } + if (!EmitTree(cx, bce, body)) return false; @@ -2576,8 +2592,10 @@ frontend::EmitFunctionScript(JSContext *cx, BytecodeEmitter *bce, ParseNode *bod * If this function is only expected to run once, mark the script so that * initializers created within it may be given more precise types. */ - if (bce->parent && bce->parent->emittingRunOnceLambda) + if (runOnce) { bce->script->treatAsRunOnce = true; + JS_ASSERT(!bce->script->hasRunOnce); + } /* Initialize fun->script() so that the debugger has a valid fun->script(). */ RootedFunction fun(cx, bce->script->function()); diff --git a/js/src/ion/BaselineCompiler.cpp b/js/src/ion/BaselineCompiler.cpp index 42de6bb7691..ec4d937aa73 100644 --- a/js/src/ion/BaselineCompiler.cpp +++ b/js/src/ion/BaselineCompiler.cpp @@ -1775,6 +1775,29 @@ BaselineCompiler::emit_JSOP_CALLALIASEDVAR() bool BaselineCompiler::emit_JSOP_SETALIASEDVAR() { + JSScript *outerScript = ScopeCoordinateFunctionScript(cx, script, pc); + if (outerScript && outerScript->treatAsRunOnce) { + // Type updates for this operation might need to be tracked, so treat + // this as a SETPROP. + + // Load rhs into R1. + frame.syncStack(1); + frame.popValue(R1); + + // Load and box lhs into R0. + getScopeCoordinateObject(R2.scratchReg()); + masm.tagValue(JSVAL_TYPE_OBJECT, R2.scratchReg(), R0); + + // Call SETPROP IC. + ICSetProp_Fallback::Compiler compiler(cx); + if (!emitOpIC(compiler.getStub(&stubSpace_))) + return false; + + // The IC will return the RHS value in R0, mark it as pushed value. + frame.push(R0); + return true; + } + // Keep rvalue in R0. frame.popRegsAndSync(1); Register objReg = R2.scratchReg(); @@ -2614,6 +2637,23 @@ BaselineCompiler::emit_JSOP_ARGUMENTS() return true; } +typedef bool (*RunOnceScriptPrologueFn)(JSContext *, HandleScript); +static const VMFunction RunOnceScriptPrologueInfo = + FunctionInfo(js::RunOnceScriptPrologue); + +bool +BaselineCompiler::emit_JSOP_RUNONCE() +{ + frame.syncStack(0); + + prepareVMCall(); + + masm.movePtr(ImmGCPtr(script), R0.scratchReg()); + pushArg(R0.scratchReg()); + + return callVM(RunOnceScriptPrologueInfo); +} + bool BaselineCompiler::emit_JSOP_REST() { diff --git a/js/src/ion/BaselineCompiler.h b/js/src/ion/BaselineCompiler.h index 93e8c2ccf28..4bb0ca9c0cc 100644 --- a/js/src/ion/BaselineCompiler.h +++ b/js/src/ion/BaselineCompiler.h @@ -158,6 +158,7 @@ namespace ion { _(JSOP_EXCEPTION) \ _(JSOP_DEBUGGER) \ _(JSOP_ARGUMENTS) \ + _(JSOP_RUNONCE) \ _(JSOP_REST) \ _(JSOP_TOID) \ _(JSOP_TABLESWITCH) \ diff --git a/js/src/ion/BaselineIC.cpp b/js/src/ion/BaselineIC.cpp index 32bb06227bd..349109f401b 100644 --- a/js/src/ion/BaselineIC.cpp +++ b/js/src/ion/BaselineIC.cpp @@ -1422,7 +1422,10 @@ DoTypeUpdateFallback(JSContext *cx, BaselineFrame *frame, ICUpdatedStub *stub, H case ICStub::SetProp_NativeAdd: { JS_ASSERT(obj->isNative()); jsbytecode *pc = stub->getChainFallback()->icEntry()->pc(script); - id = NameToId(script->getName(pc)); + if (*pc == JSOP_SETALIASEDVAR) + id = NameToId(ScopeCoordinateName(cx, script, pc)); + else + id = NameToId(script->getName(pc)); types::AddTypePropertyId(cx, obj, id, value); break; } @@ -6259,9 +6262,17 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub, JSOp op = JSOp(*pc); FallbackICSpew(cx, stub, "SetProp(%s)", js_CodeName[op]); - JS_ASSERT(op == JSOP_SETPROP || op == JSOP_SETNAME || op == JSOP_SETGNAME || op == JSOP_INITPROP); + JS_ASSERT(op == JSOP_SETPROP || + op == JSOP_SETNAME || + op == JSOP_SETGNAME || + op == JSOP_INITPROP || + op == JSOP_SETALIASEDVAR); - RootedPropertyName name(cx, script->getName(pc)); + RootedPropertyName name(cx); + if (op == JSOP_SETALIASEDVAR) + name = ScopeCoordinateName(cx, script, pc); + else + name = script->getName(pc); RootedId id(cx, NameToId(name)); RootedObject obj(cx, ToObjectFromStack(cx, lhs)); @@ -6277,6 +6288,8 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub, } else if (op == JSOP_SETNAME || op == JSOP_SETGNAME) { if (!SetNameOperation(cx, script, pc, obj, rhs)) return false; + } else if (op == JSOP_SETALIASEDVAR) { + obj->asScope().setAliasedVar(cx, pc, name, rhs); } else if (script->strict) { if (!js::SetProperty(cx, obj, id, rhs)) return false; diff --git a/js/src/ion/CodeGenerator.cpp b/js/src/ion/CodeGenerator.cpp index 4d6a04f9460..4492556d224 100644 --- a/js/src/ion/CodeGenerator.cpp +++ b/js/src/ion/CodeGenerator.cpp @@ -2789,7 +2789,7 @@ CodeGenerator::visitNewDeclEnvObject(LNewDeclEnvObject *lir) return true; } -typedef JSObject *(*NewCallObjectFn)(JSContext *, HandleShape, +typedef JSObject *(*NewCallObjectFn)(JSContext *, HandleScript, HandleShape, HandleTypeObject, HeapSlot *); static const VMFunction NewCallObjectInfo = FunctionInfo(NewCallObject); @@ -2805,25 +2805,33 @@ CodeGenerator::visitNewCallObject(LNewCallObject *lir) OutOfLineCode *ool; if (lir->slots()->isRegister()) { ool = oolCallVM(NewCallObjectInfo, lir, - (ArgList(), ImmGCPtr(templateObj->lastProperty()), - ImmGCPtr(templateObj->type()), + (ArgList(), ImmGCPtr(lir->mir()->block()->info().script()), + ImmGCPtr(templateObj->lastProperty()), + ImmGCPtr(templateObj->hasLazyType() ? NULL : templateObj->type()), ToRegister(lir->slots())), StoreRegisterTo(obj)); } else { ool = oolCallVM(NewCallObjectInfo, lir, - (ArgList(), ImmGCPtr(templateObj->lastProperty()), - ImmGCPtr(templateObj->type()), + (ArgList(), ImmGCPtr(lir->mir()->block()->info().script()), + ImmGCPtr(templateObj->lastProperty()), + ImmGCPtr(templateObj->hasLazyType() ? NULL : templateObj->type()), ImmWord((void *)NULL)), StoreRegisterTo(obj)); } if (!ool) return false; - masm.newGCThing(obj, templateObj, ool->entry()); - masm.initGCThing(obj, templateObj); + if (lir->mir()->needsSingletonType()) { + // Objects can only be given singleton types in VM calls. + masm.jump(ool->entry()); + } else { + masm.newGCThing(obj, templateObj, ool->entry()); + masm.initGCThing(obj, templateObj); + + if (lir->slots()->isRegister()) + masm.storePtr(ToRegister(lir->slots()), Address(obj, JSObject::offsetOfSlots())); + } - if (lir->slots()->isRegister()) - masm.storePtr(ToRegister(lir->slots()), Address(obj, JSObject::offsetOfSlots())); masm.bind(ool->rejoin()); return true; } @@ -4990,6 +4998,17 @@ CodeGenerator::visitGetArgument(LGetArgument *lir) return true; } +typedef bool (*RunOnceScriptPrologueFn)(JSContext *, HandleScript); +static const VMFunction RunOnceScriptPrologueInfo = + FunctionInfo(js::RunOnceScriptPrologue); + +bool +CodeGenerator::visitRunOncePrologue(LRunOncePrologue *lir) +{ + pushArg(ImmGCPtr(lir->mir()->block()->info().script())); + return callVM(RunOnceScriptPrologueInfo, lir); +} + bool CodeGenerator::emitRest(LInstruction *lir, Register array, Register numActuals, Register temp0, Register temp1, unsigned numFormals, @@ -5732,7 +5751,7 @@ CodeGenerator::visitBindNameIC(OutOfLineUpdateCache *ool, BindNameIC *ic) } typedef bool (*SetPropertyFn)(JSContext *, HandleObject, - HandlePropertyName, const HandleValue, bool, bool); + HandlePropertyName, const HandleValue, bool, int); static const VMFunction SetPropertyInfo = FunctionInfo(SetProperty); @@ -5742,10 +5761,9 @@ CodeGenerator::visitCallSetProperty(LCallSetProperty *ins) ConstantOrRegister value = TypedOrValueRegister(ToValue(ins, LCallSetProperty::Value)); const Register objReg = ToRegister(ins->getOperand(0)); - jsbytecode *pc = ins->mir()->resumePoint()->pc(); - bool isSetName = JSOp(*pc) == JSOP_SETNAME || JSOp(*pc) == JSOP_SETGNAME; + JSOp op = JSOp(*ins->mir()->resumePoint()->pc()); - pushArg(Imm32(isSetName)); + pushArg(Imm32(op)); pushArg(Imm32(ins->mir()->strict())); pushArg(value); diff --git a/js/src/ion/CodeGenerator.h b/js/src/ion/CodeGenerator.h index 4b03faf3fbe..7fa8e61f10f 100644 --- a/js/src/ion/CodeGenerator.h +++ b/js/src/ion/CodeGenerator.h @@ -215,6 +215,7 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitIteratorEnd(LIteratorEnd *lir); bool visitArgumentsLength(LArgumentsLength *lir); bool visitGetArgument(LGetArgument *lir); + bool visitRunOncePrologue(LRunOncePrologue *lir); bool emitRest(LInstruction *lir, Register array, Register numActuals, Register temp0, Register temp1, unsigned numFormals, JSObject *templateObject, const VMFunction &f); diff --git a/js/src/ion/IonBuilder.cpp b/js/src/ion/IonBuilder.cpp index d8ff77a7eae..57b2c4db4d1 100644 --- a/js/src/ion/IonBuilder.cpp +++ b/js/src/ion/IonBuilder.cpp @@ -1188,6 +1188,9 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_ARGUMENTS: return jsop_arguments(); + case JSOP_RUNONCE: + return jsop_runonce(); + case JSOP_REST: return jsop_rest(); @@ -1304,7 +1307,11 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_CALLGNAME: { RootedPropertyName name(cx, info().getAtom(pc)->asPropertyName()); - return jsop_getgname(name); + RootedObject obj(cx, &script()->global()); + bool succeeded; + if (!getStaticName(obj, name, &succeeded)) + return false; + return succeeded || jsop_getname(name); } case JSOP_BINDGNAME: @@ -1313,7 +1320,8 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_SETGNAME: { RootedPropertyName name(cx, info().getAtom(pc)->asPropertyName()); - return jsop_setgname(name); + RootedObject obj(cx, &script()->global()); + return setStaticName(obj, name); } case JSOP_NAME: @@ -4281,8 +4289,9 @@ IonBuilder::createCallObject(MDefinition *callee, MDefinition *scope) // Allocate the actual object. It is important that no intervening // instructions could potentially bailout, thus leaking the dynamic slots - // pointer. - MInstruction *callObj = MNewCallObject::New(templateObj, slots); + // pointer. Run-once scripts need a singleton type, so always do a VM call + // in such cases. + MInstruction *callObj = MNewCallObject::New(templateObj, script()->treatAsRunOnce, slots); current->add(callObj); // Initialize the object's reserved slots. No post barrier is needed here, @@ -5902,44 +5911,50 @@ IonBuilder::pushTypeBarrier(MInstruction *ins, types::StackTypeSet *observed, bo } bool -IonBuilder::jsop_getgname(HandlePropertyName name) +IonBuilder::getStaticName(HandleObject staticObject, HandlePropertyName name, bool *psucceeded) { - // Optimize undefined, NaN, and Infinity. - if (name == cx->names().undefined) - return pushConstant(UndefinedValue()); - if (name == cx->names().NaN) - return pushConstant(cx->runtime->NaNValue); - if (name == cx->names().Infinity) - return pushConstant(cx->runtime->positiveInfinityValue); + JS_ASSERT(staticObject->isGlobal() || staticObject->isCall()); - RootedObject globalObj(cx, &script()->global()); - JS_ASSERT(globalObj->isNative()); + *psucceeded = true; + + if (staticObject->isGlobal()) { + // Optimize undefined, NaN, and Infinity. + if (name == cx->names().undefined) + return pushConstant(UndefinedValue()); + if (name == cx->names().NaN) + return pushConstant(cx->runtime->NaNValue); + if (name == cx->names().Infinity) + return pushConstant(cx->runtime->positiveInfinityValue); + } RootedId id(cx, NameToId(name)); // For the fastest path, the property must be found, and it must be found // as a normal data property on exactly the global object. - RootedShape shape(cx, globalObj->nativeLookup(cx, id)); - if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) - return jsop_getname(name); + RootedShape shape(cx, staticObject->nativeLookup(cx, id)); + if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) { + *psucceeded = false; + return true; + } - types::TypeObject *globalType = globalObj->getType(cx); - if (!globalType) + types::TypeObject *staticType = staticObject->getType(cx); + if (!staticType) return false; types::HeapTypeSet *propertyTypes = NULL; - if (!globalType->unknownProperties()) { - propertyTypes = globalType->getProperty(cx, id, false); + if (!staticType->unknownProperties()) { + propertyTypes = staticType->getProperty(cx, id, false); if (!propertyTypes) return false; } - if (propertyTypes && propertyTypes->isOwnProperty(cx, globalType, true)) { + if (propertyTypes && propertyTypes->isOwnProperty(cx, staticType, true)) { // The property has been reconfigured as non-configurable, non-enumerable // or non-writable. - return jsop_getname(name); + *psucceeded = false; + return true; } types::StackTypeSet *types = types::TypeScript::BytecodeTypes(script(), pc); - bool barrier = PropertyReadNeedsTypeBarrier(cx, globalType, name, types); + bool barrier = PropertyReadNeedsTypeBarrier(cx, staticType, name, types); // If the property is permanent, a shape guard isn't necessary. @@ -5950,7 +5965,7 @@ IonBuilder::jsop_getgname(HandlePropertyName name) if (singleton) { // Try to inline a known constant value. bool isKnownConstant; - if (!TestSingletonProperty(cx, globalObj, singleton, id, &isKnownConstant)) + if (!TestSingletonProperty(cx, staticObject, singleton, id, &isKnownConstant)) return false; if (isKnownConstant) return pushConstant(ObjectValue(*singleton)); @@ -5961,27 +5976,19 @@ IonBuilder::jsop_getgname(HandlePropertyName name) return pushConstant(NullValue()); } - MInstruction *global = MConstant::New(ObjectValue(*globalObj)); - current->add(global); + MInstruction *obj = MConstant::New(ObjectValue(*staticObject)); + current->add(obj); // If we have a property typeset, the isOwnProperty call will trigger recompilation if // the property is deleted or reconfigured. if (!propertyTypes && shape->configurable()) - global = addShapeGuard(global, globalObj->lastProperty(), Bailout_ShapeGuard); + obj = addShapeGuard(obj, staticObject->lastProperty(), Bailout_ShapeGuard); - JS_ASSERT(shape->slot() >= globalObj->numFixedSlots()); + MIRType rvalType = MIRTypeFromValueType(types->getKnownTypeTag()); + if (barrier) + rvalType = MIRType_Value; - MSlots *slots = MSlots::New(global); - current->add(slots); - MLoadSlot *load = MLoadSlot::New(slots, shape->slot() - globalObj->numFixedSlots()); - current->add(load); - - // Slot loads can be typed, if they have a single, known, definitive type. - if (knownType != JSVAL_TYPE_UNKNOWN && !barrier) - load->setResultType(MIRTypeFromValueType(knownType)); - - current->push(load); - return pushTypeBarrier(load, types, barrier); + return loadSlot(obj, shape, rvalType, barrier, types); } // Whether 'types' includes all possible values represented by input/inputTypes. @@ -6018,34 +6025,33 @@ ion::NeedsPostBarrier(CompileInfo &info, MDefinition *value) } bool -IonBuilder::jsop_setgname(HandlePropertyName name) +IonBuilder::setStaticName(HandleObject staticObject, HandlePropertyName name) { - RootedObject globalObj(cx, &script()->global()); RootedId id(cx, NameToId(name)); - JS_ASSERT(globalObj->isNative()); + JS_ASSERT(staticObject->isGlobal() || staticObject->isCall()); MDefinition *value = current->peek(-1); - if (globalObj->watched()) + if (staticObject->watched()) return jsop_setprop(name); // For the fastest path, the property must be found, and it must be found // as a normal data property on exactly the global object. - RootedShape shape(cx, globalObj->nativeLookup(cx, id)); + RootedShape shape(cx, staticObject->nativeLookup(cx, id)); if (!shape || !shape->hasDefaultSetter() || !shape->writable() || !shape->hasSlot()) return jsop_setprop(name); - types::TypeObject *globalType = globalObj->getType(cx); - if (!globalType) + types::TypeObject *staticType = staticObject->getType(cx); + if (!staticType) return false; types::HeapTypeSet *propertyTypes = NULL; - if (!globalType->unknownProperties()) { - propertyTypes = globalType->getProperty(cx, id, false); + if (!staticType->unknownProperties()) { + propertyTypes = staticType->getProperty(cx, id, false); if (!propertyTypes) return false; } - if (!propertyTypes || propertyTypes->isOwnProperty(cx, globalType, true)) { + if (!propertyTypes || propertyTypes->isOwnProperty(cx, staticType, true)) { // The property has been reconfigured as non-configurable, non-enumerable // or non-writable. return jsop_setprop(name); @@ -6053,48 +6059,36 @@ IonBuilder::jsop_setgname(HandlePropertyName name) if (!TypeSetIncludes(propertyTypes, value->type(), value->resultTypeSet())) return jsop_setprop(name); - MInstruction *global = MConstant::New(ObjectValue(*globalObj)); - current->add(global); + current->pop(); + + // Pop the bound object on the stack. + MDefinition *obj = current->pop(); + JS_ASSERT(&obj->toConstant()->value().toObject() == staticObject); // If we have a property type set, the isOwnProperty call will trigger recompilation // if the property is deleted or reconfigured. Without TI, we always need a shape guard // to guard against the property being reconfigured as non-writable. if (!propertyTypes) - global = addShapeGuard(global, globalObj->lastProperty(), Bailout_ShapeGuard); - - JS_ASSERT(shape->slot() >= globalObj->numFixedSlots()); - - MSlots *slots = MSlots::New(global); - current->add(slots); + obj = addShapeGuard(obj, staticObject->lastProperty(), Bailout_ShapeGuard); // Note: we do not use a post barrier when writing to the global object. // Slots in the global object will be treated as roots during a minor GC. - current->pop(); - MStoreSlot *store = MStoreSlot::New(slots, shape->slot() - globalObj->numFixedSlots(), value); - current->add(store); - - // Determine whether write barrier is required. - if (!propertyTypes || propertyTypes->needsBarrier(cx)) - store->setNeedsBarrier(); - - // Pop the global object pushed by bindgname. - DebugOnly pushedGlobal = current->pop(); - JS_ASSERT(&pushedGlobal->toConstant()->value().toObject() == globalObj); + if (!staticObject->isGlobal() && NeedsPostBarrier(info(), value)) + current->add(MPostWriteBarrier::New(obj, value)); // If the property has a known type, we may be able to optimize typed stores by not // storing the type tag. This only works if the property does not have its initial // |undefined| value; if |undefined| is assigned at a later point, it will be added // to the type set. - if (propertyTypes && !globalObj->getSlot(shape->slot()).isUndefined()) { + MIRType slotType = MIRType_None; + if (propertyTypes && !staticObject->getSlot(shape->slot()).isUndefined()) { JSValueType knownType = propertyTypes->getKnownTypeTag(cx); if (knownType != JSVAL_TYPE_UNKNOWN) - store->setSlotType(MIRTypeFromValueType(knownType)); + slotType = MIRTypeFromValueType(knownType); } - JS_ASSERT_IF(store->needsBarrier(), store->slotType() != MIRType_None); - - current->push(value); - return resumeAfter(store); + bool needsBarrier = !propertyTypes || propertyTypes->needsBarrier(cx); + return storeSlot(obj, shape, value, needsBarrier, slotType); } bool @@ -7069,6 +7063,14 @@ GetDefiniteSlot(JSContext *cx, types::StackTypeSet *types, JSAtom *atom) return propertyTypes; } +bool +IonBuilder::jsop_runonce() +{ + MRunOncePrologue *ins = MRunOncePrologue::New(); + current->add(ins); + return resumeAfter(ins); +} + bool IonBuilder::jsop_not() { @@ -7415,7 +7417,8 @@ IonBuilder::loadSlot(MDefinition *obj, Shape *shape, MIRType rvalType, } bool -IonBuilder::storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool needsBarrier) +IonBuilder::storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool needsBarrier, + MIRType slotType /* = MIRType_None */) { JS_ASSERT(shape->hasDefaultSetter()); JS_ASSERT(shape->writable()); @@ -7438,6 +7441,8 @@ IonBuilder::storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool n current->push(value); if (needsBarrier) store->setNeedsBarrier(); + if (slotType != MIRType_None) + store->setSlotType(slotType); return resumeAfter(store); } @@ -8150,9 +8155,74 @@ IonBuilder::walkScopeChain(unsigned hops) return scope; } +bool +IonBuilder::hasStaticScopeObject(ScopeCoordinate sc, MutableHandleObject pcall) +{ + JSScript *outerScript = ScopeCoordinateFunctionScript(cx, script(), pc); + if (!outerScript || !outerScript->treatAsRunOnce) + return false; + + types::TypeObject *funType = outerScript->function()->getType(cx); + if (!funType) + return false; + if (types::HeapTypeSet::HasObjectFlags(cx, funType, types::OBJECT_FLAG_RUNONCE_INVALIDATED)) + return false; + + // The script this aliased var operation is accessing will run only once, + // so there will be only one call object and the aliased var access can be + // compiled in the same manner as a global access. We still need to find + // the call object though. + + // Look for the call object on the current script's function's scope chain. + // If the current script is inner to the outer script and the function has + // singleton type then it should show up here. + + MDefinition *scope = current->getSlot(info().scopeChainSlot()); + scope->setFoldedUnchecked(); + + JSObject *environment = script()->function()->environment(); + while (environment && !environment->isGlobal()) { + if (environment->isCall() && + !environment->asCall().isForEval() && + environment->asCall().callee().nonLazyScript() == outerScript) + { + JS_ASSERT(environment->hasSingletonType()); + pcall.set(environment); + return true; + } + environment = environment->enclosingScope(); + } + + // Look for the call object on the current frame, if we are compiling the + // outer script itself. Don't do this if we are at entry to the outer + // script, as the call object we see will not be the real one --- after + // entering the Ion code a different call object will be created. + + if (script() == outerScript && fp && info().osrPc()) { + JSObject *scope = fp.scopeChain(); + if (scope->isCall() && scope->asCall().callee().nonLazyScript() == outerScript) { + JS_ASSERT(scope->hasSingletonType()); + pcall.set(scope); + return true; + } + } + + return true; +} + bool IonBuilder::jsop_getaliasedvar(ScopeCoordinate sc) { + RootedObject call(cx); + if (hasStaticScopeObject(sc, &call) && call) { + RootedPropertyName name(cx, ScopeCoordinateName(cx, script(), pc)); + bool succeeded; + if (!getStaticName(call, name, &succeeded)) + return false; + if (succeeded) + return true; + } + MDefinition *obj = walkScopeChain(sc.hops); RootedShape shape(cx, ScopeCoordinateToStaticScopeShape(cx, script(), pc)); @@ -8177,6 +8247,34 @@ IonBuilder::jsop_getaliasedvar(ScopeCoordinate sc) bool IonBuilder::jsop_setaliasedvar(ScopeCoordinate sc) { + RootedObject call(cx); + if (hasStaticScopeObject(sc, &call)) { + uint32_t depth = current->stackDepth() + 1; + if (depth > current->nslots()) { + if (!current->increaseSlots(depth - current->nslots())) + return false; + } + MDefinition *value = current->pop(); + RootedPropertyName name(cx, ScopeCoordinateName(cx, script(), pc)); + + if (call) { + // Push the object on the stack to match the bound object expected in + // the global and property set cases. + MInstruction *constant = MConstant::New(ObjectValue(*call)); + current->add(constant); + current->push(constant); + current->push(value); + return setStaticName(call, name); + } + + // The call object has type information we need to respect but we + // couldn't find it. Just do a normal property assign. + MDefinition *obj = walkScopeChain(sc.hops); + current->push(obj); + current->push(value); + return jsop_setprop(name); + } + MDefinition *rval = current->peek(-1); MDefinition *obj = walkScopeChain(sc.hops); diff --git a/js/src/ion/IonBuilder.h b/js/src/ion/IonBuilder.h index 2d0201f8895..e0bb8828d11 100644 --- a/js/src/ion/IonBuilder.h +++ b/js/src/ion/IonBuilder.h @@ -339,9 +339,11 @@ class IonBuilder : public MIRGenerator bool invalidatedIdempotentCache(); + bool hasStaticScopeObject(ScopeCoordinate sc, MutableHandleObject pcall); bool loadSlot(MDefinition *obj, Shape *shape, MIRType rvalType, bool barrier, types::StackTypeSet *types); - bool storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool needsBarrier); + bool storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool needsBarrier, + MIRType slotType = MIRType_None); // jsop_getprop() helpers. bool getPropTryArgumentsLength(bool *emitted); @@ -381,8 +383,8 @@ class IonBuilder : public MIRGenerator bool jsop_dup2(); bool jsop_loophead(jsbytecode *pc); bool jsop_compare(JSOp op); - bool jsop_getgname(HandlePropertyName name); - bool jsop_setgname(HandlePropertyName name); + bool getStaticName(HandleObject staticObject, HandlePropertyName name, bool *psucceeded); + bool setStaticName(HandleObject staticObject, HandlePropertyName name); bool jsop_getname(HandlePropertyName name); bool jsop_intrinsic(HandlePropertyName name); bool jsop_bindname(PropertyName *name); @@ -406,6 +408,7 @@ class IonBuilder : public MIRGenerator bool jsop_arguments_length(); bool jsop_arguments_getelem(); bool jsop_arguments_setelem(MDefinition *object, MDefinition *index, MDefinition *value); + bool jsop_runonce(); bool jsop_rest(); bool jsop_not(); bool jsop_getprop(HandlePropertyName name); diff --git a/js/src/ion/LIR-Common.h b/js/src/ion/LIR-Common.h index 8a4b70c82f9..5ee2aadee5e 100644 --- a/js/src/ion/LIR-Common.h +++ b/js/src/ion/LIR-Common.h @@ -4158,6 +4158,16 @@ class LGetArgument : public LInstructionHelper } }; +class LRunOncePrologue : public LCallInstructionHelper<0, 0, 0> +{ + public: + LIR_HEADER(RunOncePrologue) + + MRunOncePrologue *mir() const { + return mir_->toRunOncePrologue(); + } +}; + // Create the rest parameter. class LRest : public LCallInstructionHelper<1, 1, 3> { @@ -4203,7 +4213,6 @@ class LParRest : public LCallInstructionHelper<1, 2, 3> } }; - class LParWriteGuard : public LCallInstructionHelper<0, 2, 1> { public: diff --git a/js/src/ion/LOpcodes.h b/js/src/ion/LOpcodes.h index 924e1ac2c2c..72115c438e6 100644 --- a/js/src/ion/LOpcodes.h +++ b/js/src/ion/LOpcodes.h @@ -207,6 +207,7 @@ _(StringLength) \ _(ArgumentsLength) \ _(GetArgument) \ + _(RunOncePrologue) \ _(Rest) \ _(ParRest) \ _(TypeOfV) \ diff --git a/js/src/ion/Lowering.cpp b/js/src/ion/Lowering.cpp index 82a95159307..e851dc9216c 100644 --- a/js/src/ion/Lowering.cpp +++ b/js/src/ion/Lowering.cpp @@ -2499,6 +2499,13 @@ LIRGenerator::visitGetArgument(MGetArgument *ins) return defineBox(lir, ins); } +bool +LIRGenerator::visitRunOncePrologue(MRunOncePrologue *ins) +{ + LRunOncePrologue *lir = new LRunOncePrologue; + return add(lir, ins) && assignSafepoint(lir, ins); +} + bool LIRGenerator::visitRest(MRest *ins) { diff --git a/js/src/ion/Lowering.h b/js/src/ion/Lowering.h index ccc1f35ee02..c73164a3b6e 100644 --- a/js/src/ion/Lowering.h +++ b/js/src/ion/Lowering.h @@ -220,6 +220,7 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitStringLength(MStringLength *ins); bool visitArgumentsLength(MArgumentsLength *ins); bool visitGetArgument(MGetArgument *ins); + bool visitRunOncePrologue(MRunOncePrologue *ins); bool visitRest(MRest *ins); bool visitParRest(MParRest *ins); bool visitThrow(MThrow *ins); diff --git a/js/src/ion/MIR.h b/js/src/ion/MIR.h index 50ad5a12f93..66837866ebb 100644 --- a/js/src/ion/MIR.h +++ b/js/src/ion/MIR.h @@ -2278,6 +2278,23 @@ class MSetArgumentsObjectArg } }; +class MRunOncePrologue + : public MNullaryInstruction +{ + protected: + MRunOncePrologue() + { + setGuard(); + } + + public: + INSTRUCTION_HEADER(RunOncePrologue) + + static MRunOncePrologue *New() { + return new MRunOncePrologue(); + } +}; + // Given a MIRType_Value A and a MIRType_Object B: // If the Value may be safely unboxed to an Object, return Object(A). // Otherwise, return B. @@ -7400,10 +7417,12 @@ class MNewDeclEnvObject : public MNullaryInstruction class MNewCallObject : public MUnaryInstruction { CompilerRootObject templateObj_; + bool needsSingletonType_; - MNewCallObject(HandleObject templateObj, MDefinition *slots) + MNewCallObject(HandleObject templateObj, bool needsSingletonType, MDefinition *slots) : MUnaryInstruction(slots), - templateObj_(templateObj) + templateObj_(templateObj), + needsSingletonType_(needsSingletonType) { setResultType(MIRType_Object); } @@ -7411,8 +7430,8 @@ class MNewCallObject : public MUnaryInstruction public: INSTRUCTION_HEADER(NewCallObject) - static MNewCallObject *New(HandleObject templateObj, MDefinition *slots) { - return new MNewCallObject(templateObj, slots); + static MNewCallObject *New(HandleObject templateObj, bool needsSingletonType, MDefinition *slots) { + return new MNewCallObject(templateObj, needsSingletonType, slots); } MDefinition *slots() { @@ -7421,6 +7440,9 @@ class MNewCallObject : public MUnaryInstruction JSObject *templateObject() { return templateObj_; } + bool needsSingletonType() { + return needsSingletonType_; + } AliasSet getAliasSet() const { return AliasSet::None(); } diff --git a/js/src/ion/MOpcodes.h b/js/src/ion/MOpcodes.h index 89a5a6cc65b..be558af0cb9 100644 --- a/js/src/ion/MOpcodes.h +++ b/js/src/ion/MOpcodes.h @@ -155,6 +155,7 @@ namespace ion { _(StringLength) \ _(ArgumentsLength) \ _(GetArgument) \ + _(RunOncePrologue) \ _(Rest) \ _(Floor) \ _(Round) \ diff --git a/js/src/ion/ParallelArrayAnalysis.cpp b/js/src/ion/ParallelArrayAnalysis.cpp index f8c0c57dca6..c33c3e338ce 100644 --- a/js/src/ion/ParallelArrayAnalysis.cpp +++ b/js/src/ion/ParallelArrayAnalysis.cpp @@ -240,6 +240,7 @@ class ParallelArrayVisitor : public MInstructionVisitor SAFE_OP(StringLength) UNSAFE_OP(ArgumentsLength) UNSAFE_OP(GetArgument) + UNSAFE_OP(RunOncePrologue) CUSTOM_OP(Rest) SAFE_OP(ParRest) SAFE_OP(Floor) diff --git a/js/src/ion/VMFunctions.cpp b/js/src/ion/VMFunctions.cpp index 9db927d1795..4a6416f1aba 100644 --- a/js/src/ion/VMFunctions.cpp +++ b/js/src/ion/VMFunctions.cpp @@ -390,13 +390,22 @@ StringFromCharCode(JSContext *cx, int32_t code) bool SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue value, - bool strict, bool isSetName) + bool strict, int jsop) { RootedValue v(cx, value); RootedId id(cx, NameToId(name)); + if (jsop == JSOP_SETALIASEDVAR) { + // Aliased var assigns ignore readonly attributes on the property, as + // required for initializing 'const' closure variables. + Shape *shape = obj->nativeLookup(cx, name); + JS_ASSERT(shape && shape->hasSlot()); + JSObject::nativeSetSlotWithType(cx, obj, shape, value); + return true; + } + if (JS_LIKELY(!obj->getOps()->setProperty)) { - unsigned defineHow = isSetName ? DNP_UNQUALIFIED : 0; + unsigned defineHow = (jsop == JSOP_SETNAME || jsop == JSOP_SETGNAME) ? DNP_UNQUALIFIED : 0; return baseops::SetPropertyHelper(cx, obj, obj, id, defineHow, &v, strict); } @@ -427,9 +436,10 @@ NewSlots(JSRuntime *rt, unsigned nslots) } JSObject * -NewCallObject(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots) +NewCallObject(JSContext *cx, HandleScript script, + HandleShape shape, HandleTypeObject type, HeapSlot *slots) { - return CallObject::create(cx, shape, type, slots); + return CallObject::create(cx, script, shape, type, slots); } JSObject * diff --git a/js/src/ion/VMFunctions.h b/js/src/ion/VMFunctions.h index 86686d2aae5..17bebf15d99 100644 --- a/js/src/ion/VMFunctions.h +++ b/js/src/ion/VMFunctions.h @@ -510,12 +510,13 @@ bool CharCodeAt(JSContext *cx, HandleString str, int32_t index, uint32_t *code); JSFlatString *StringFromCharCode(JSContext *cx, int32_t code); bool SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue value, - bool strict, bool isSetName); + bool strict, int jsop); bool InterruptCheck(JSContext *cx); HeapSlot *NewSlots(JSRuntime *rt, unsigned nslots); -JSObject *NewCallObject(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots); +JSObject *NewCallObject(JSContext *cx, HandleScript script, + HandleShape shape, HandleTypeObject type, HeapSlot *slots); JSObject *NewStringObject(JSContext *cx, HandleString str); bool SPSEnter(JSContext *cx, HandleScript script); diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 677c55a55e9..1b1b1324519 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -4120,6 +4120,7 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, TypeInferen case JSOP_TABLESWITCH: case JSOP_TRY: case JSOP_LABEL: + case JSOP_RUNONCE: break; /* Bytecodes pushing values of known type. */ diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index e7cae47bfc5..5f769abddb5 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -395,8 +395,14 @@ enum { /* Whether any objects emulate undefined; see EmulatesUndefined. */ OBJECT_FLAG_EMULATES_UNDEFINED = 0x00400000, + /* + * For the function on a run-once script, whether the function has actually + * run multiple times. + */ + OBJECT_FLAG_RUNONCE_INVALIDATED = 0x00800000, + /* Flags which indicate dynamic properties of represented objects. */ - OBJECT_FLAG_DYNAMIC_MASK = 0x007f0000, + OBJECT_FLAG_DYNAMIC_MASK = 0x00ff0000, /* * Whether all properties of this object are considered unknown. diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index 2b77aafb0a5..7229e1b7b23 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -13,6 +13,8 @@ #include "jscompartment.h" #include "jsinfer.h" #include "jsprf.h" +#include "jsproxy.h" +#include "jstypedarray.h" #include "builtin/ParallelArray.h" #ifdef JS_ION diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index c6f7f589c60..5b70a099fe6 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -1274,7 +1274,6 @@ js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode, bool /* No-ops for ease of decompilation. */ ADD_EMPTY_CASE(JSOP_NOP) -ADD_EMPTY_CASE(JSOP_UNUSED71) ADD_EMPTY_CASE(JSOP_UNUSED125) ADD_EMPTY_CASE(JSOP_UNUSED126) ADD_EMPTY_CASE(JSOP_UNUSED132) @@ -2463,6 +2462,13 @@ BEGIN_CASE(JSOP_ARGUMENTS) } END_CASE(JSOP_ARGUMENTS) +BEGIN_CASE(JSOP_RUNONCE) +{ + if (!RunOnceScriptPrologue(cx, script)) + goto error; +} +END_CASE(JSOP_RUNONCE) + BEGIN_CASE(JSOP_REST) { RootedObject &rest = rootObject0; @@ -2485,7 +2491,13 @@ END_CASE(JSOP_GETALIASEDVAR) BEGIN_CASE(JSOP_SETALIASEDVAR) { ScopeCoordinate sc = ScopeCoordinate(regs.pc); - regs.fp()->aliasedVarScope(sc).setAliasedVar(sc, regs.sp[-1]); + ScopeObject &obj = regs.fp()->aliasedVarScope(sc); + + // Avoid computing the name if no type updates are needed, as this may be + // expensive on scopes with large numbers of variables. + PropertyName *name = obj.hasSingletonType() ? ScopeCoordinateName(cx, script, regs.pc) : NULL; + + obj.setAliasedVar(cx, sc, name, regs.sp[-1]); } END_CASE(JSOP_SETALIASEDVAR) @@ -3532,6 +3544,25 @@ js::ImplicitThisOperation(JSContext *cx, HandleObject scopeObj, HandlePropertyNa return ComputeImplicitThis(cx, obj, res); } +bool +js::RunOnceScriptPrologue(JSContext *cx, HandleScript script) +{ + JS_ASSERT(script->treatAsRunOnce); + + if (!script->hasRunOnce) { + script->hasRunOnce = true; + return true; + } + + // Force instantiation of the script's function's type to ensure the flag + // is preserved in type information. + if (!script->function()->getType(cx)) + return false; + + types::MarkTypeObjectFlags(cx, script->function(), types::OBJECT_FLAG_RUNONCE_INVALIDATED); + return true; +} + bool js::InitGetterSetterOperation(JSContext *cx, jsbytecode *pc, HandleObject obj, HandleId id, HandleValue val) @@ -3583,5 +3614,6 @@ js::InitGetterSetterOperation(JSContext *cx, jsbytecode *pc, HandleObject obj, H RootedId id(cx); if (!ValueToId(cx, idval, &id)) return false; + return InitGetterSetterOperation(cx, pc, obj, id, val); } diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index 60a8e549c0a..610d4ba2d29 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -417,6 +417,9 @@ IteratorMore(JSContext *cx, JSObject *iterobj, bool *cond, MutableHandleValue rv bool IteratorNext(JSContext *cx, HandleObject iterobj, MutableHandleValue rval); +bool +RunOnceScriptPrologue(JSContext *cx, HandleScript script); + bool InitGetterSetterOperation(JSContext *cx, jsbytecode *pc, HandleObject obj, HandleId id, HandleValue val); diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl index 2c289e8852c..b8229b3b9ea 100644 --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -123,7 +123,12 @@ OPDEF(JSOP_AND, 69, "and", NULL, 5, 1, 1, JOF_JUMP|JOF_DE /* The switch bytecodes have variable length. */ OPDEF(JSOP_TABLESWITCH, 70, "tableswitch", NULL, -1, 1, 0, JOF_TABLESWITCH|JOF_DETECTING) -OPDEF(JSOP_UNUSED71, 71, "unused71", NULL, 1, 0, 0, JOF_BYTE) + +/* + * Prologue emitted in scripts expected to run once, which deoptimizes code if + * it executes multiple times. + */ +OPDEF(JSOP_RUNONCE, 71, "runonce", NULL, 1, 0, 0, JOF_BYTE) /* New, infallible/transitive identity ops. */ OPDEF(JSOP_STRICTEQ, 72, "stricteq", "===", 1, 2, 1, JOF_BYTE|JOF_DETECTING|JOF_LEFTASSOC|JOF_ARITH) diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 47d7363d136..41ba293cc55 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -2810,7 +2810,7 @@ js::SetFrameArgumentsObject(JSContext *cx, AbstractFramePtr frame, JS_ASSERT(*pc == JSOP_SETALIASEDVAR); if (frame.callObj().asScope().aliasedVar(pc).isMagic(JS_OPTIMIZED_ARGUMENTS)) - frame.callObj().asScope().setAliasedVar(pc, ObjectValue(*argsobj)); + frame.callObj().asScope().setAliasedVar(cx, pc, cx->names().arguments, ObjectValue(*argsobj)); } else { if (frame.unaliasedLocal(var).isMagic(JS_OPTIMIZED_ARGUMENTS)) frame.unaliasedLocal(var) = ObjectValue(*argsobj); diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 37090a25e88..5e8be4c44ae 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -412,6 +412,7 @@ class JSScript : public js::gc::Cell script */ bool hasSingletons:1; /* script has singleton objects */ bool treatAsRunOnce:1; /* script is a lambda to treat as running once. */ + bool hasRunOnce:1; /* if treatAsRunOnce, whether script has executed. */ bool hasBeenCloned:1; /* script has been reused for a clone. */ bool isActiveEval:1; /* script came from eval(), and is still active */ bool isCachedEval:1; /* script came from eval(), and is in eval cache */ diff --git a/js/src/vm/ArgumentsObject-inl.h b/js/src/vm/ArgumentsObject-inl.h index c442eda6e55..59518e9fe3e 100644 --- a/js/src/vm/ArgumentsObject-inl.h +++ b/js/src/vm/ArgumentsObject-inl.h @@ -81,7 +81,7 @@ ArgumentsObject::element(uint32_t i) const } inline void -ArgumentsObject::setElement(uint32_t i, const Value &v) +ArgumentsObject::setElement(JSContext *cx, uint32_t i, const Value &v) { JS_ASSERT(!isElementDeleted(i)); HeapValue &lhs = data()->args[i]; @@ -89,7 +89,7 @@ ArgumentsObject::setElement(uint32_t i, const Value &v) CallObject &callobj = getFixedSlot(MAYBE_CALL_SLOT).toObject().asCall(); for (AliasedFormalIter fi(callobj.callee().nonLazyScript()); ; fi++) { if (fi.frameIndex() == i) { - callobj.setAliasedVar(fi, v); + callobj.setAliasedVar(cx, fi, fi->name(), v); return; } } diff --git a/js/src/vm/ArgumentsObject.cpp b/js/src/vm/ArgumentsObject.cpp index 158f8bdc4db..338ba80ce8f 100644 --- a/js/src/vm/ArgumentsObject.cpp +++ b/js/src/vm/ArgumentsObject.cpp @@ -340,7 +340,7 @@ ArgSetter(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, MutableHa if (JSID_IS_INT(id)) { unsigned arg = unsigned(JSID_TO_INT(id)); if (arg < argsobj.initialLength() && !argsobj.isElementDeleted(arg)) { - argsobj.setElement(arg, vp); + argsobj.setElement(cx, arg, vp); if (arg < script->function()->nargs) types::TypeScript::SetArgument(cx, script, arg, vp); return true; @@ -463,7 +463,7 @@ StrictArgSetter(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Mut if (JSID_IS_INT(id)) { unsigned arg = unsigned(JSID_TO_INT(id)); if (arg < argsobj->initialLength()) { - argsobj->setElement(arg, vp); + argsobj->setElement(cx, arg, vp); return true; } } else { diff --git a/js/src/vm/ArgumentsObject.h b/js/src/vm/ArgumentsObject.h index aaa499dd44b..88fa149652e 100644 --- a/js/src/vm/ArgumentsObject.h +++ b/js/src/vm/ArgumentsObject.h @@ -177,7 +177,7 @@ class ArgumentsObject : public JSObject * needed, the frontend should have emitted JSOP_GETALIASEDVAR. */ inline const Value &element(uint32_t i) const; - inline void setElement(uint32_t i, const Value &v); + inline void setElement(JSContext *cx, uint32_t i, const Value &v); inline const Value &arg(unsigned i) const; inline void setArg(unsigned i, const Value &v); diff --git a/js/src/vm/ScopeObject-inl.h b/js/src/vm/ScopeObject-inl.h index d85c8879ac2..711b6e2f282 100644 --- a/js/src/vm/ScopeObject-inl.h +++ b/js/src/vm/ScopeObject-inl.h @@ -9,6 +9,7 @@ #include "ScopeObject.h" +#include "jsinferinlines.h" #include "jsscriptinlines.h" namespace js { @@ -42,11 +43,17 @@ ScopeObject::aliasedVar(ScopeCoordinate sc) } inline void -ScopeObject::setAliasedVar(ScopeCoordinate sc, const Value &v) +ScopeObject::setAliasedVar(JSContext *cx, ScopeCoordinate sc, PropertyName *name, const Value &v) { JS_ASSERT(isCall() || isClonedBlock()); JS_STATIC_ASSERT(CallObject::RESERVED_SLOTS == BlockObject::RESERVED_SLOTS); + + // name may be null for non-singletons, whose types do not need to be tracked. + JS_ASSERT_IF(hasSingletonType(), name); + setSlot(sc.slot, v); + if (hasSingletonType()) + types::AddTypePropertyId(cx, this, NameToId(name), v); } /*static*/ inline size_t @@ -77,9 +84,12 @@ CallObject::aliasedVar(AliasedFormalIter fi) } inline void -CallObject::setAliasedVar(AliasedFormalIter fi, const Value &v) +CallObject::setAliasedVar(JSContext *cx, AliasedFormalIter fi, PropertyName *name, const Value &v) { + JS_ASSERT(name == fi->name()); setSlot(fi.scopeSlot(), v); + if (hasSingletonType()) + types::AddTypePropertyId(cx, this, NameToId(name), v); } /*static*/ inline size_t diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 0ccc84fb8e7..2f51b588ae9 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -28,7 +28,7 @@ typedef Rooted RootedArgumentsObject; /*****************************************************************************/ -StaticScopeIter::StaticScopeIter(JSContext *cx, HandleObject objArg) +StaticScopeIter::StaticScopeIter(JSContext *cx, JSObject *objArg) : obj(cx, objArg), onNamedLambda(false) { JS_ASSERT_IF(obj, obj->isStaticBlock() || obj->isFunction()); @@ -97,20 +97,23 @@ StaticScopeIter::funScript() const /*****************************************************************************/ -Shape * -js::ScopeCoordinateToStaticScopeShape(JSContext *cx, JSScript *script, jsbytecode *pc) +static JSObject * +InnermostStaticScope(JSScript *script, jsbytecode *pc) { JS_ASSERT(pc >= script->code && pc < script->code + script->length); JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD); uint32_t blockIndex = GET_UINT32_INDEX(pc + 2 * sizeof(uint16_t)); - RootedObject innermostStaticScope(cx, NULL); - if (blockIndex == UINT32_MAX) - innermostStaticScope = script->function(); - else - innermostStaticScope = &script->getObject(blockIndex)->asStaticBlock(); - StaticScopeIter ssi(cx, innermostStaticScope); + if (blockIndex == UINT32_MAX) + return script->function(); + return &script->getObject(blockIndex)->asStaticBlock(); +} + +Shape * +js::ScopeCoordinateToStaticScopeShape(JSContext *cx, JSScript *script, jsbytecode *pc) +{ + StaticScopeIter ssi(cx, InnermostStaticScope(script, pc)); ScopeCoordinate sc(pc); while (true) { if (ssi.hasDynamicScopeObject()) { @@ -138,6 +141,24 @@ js::ScopeCoordinateName(JSContext *cx, JSScript *script, jsbytecode *pc) return JSID_TO_ATOM(id)->asPropertyName(); } +JSScript * +js::ScopeCoordinateFunctionScript(JSContext *cx, JSScript *script, jsbytecode *pc) +{ + StaticScopeIter ssi(cx, InnermostStaticScope(script, pc)); + ScopeCoordinate sc(pc); + while (true) { + if (ssi.hasDynamicScopeObject()) { + if (!sc.hops) + break; + sc.hops--; + } + ssi++; + } + if (ssi.type() != StaticScopeIter::FUNCTION) + return NULL; + return ssi.funScript(); +} + /*****************************************************************************/ /* @@ -145,16 +166,24 @@ js::ScopeCoordinateName(JSContext *cx, JSScript *script, jsbytecode *pc) * The call object must be further initialized to be usable. */ CallObject * -CallObject::create(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots) +CallObject::create(JSContext *cx, HandleScript script, HandleShape shape, HandleTypeObject type, HeapSlot *slots) { gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); JS_ASSERT(CanBeFinalizedInBackground(kind, &CallClass)); kind = gc::GetBackgroundAllocKind(kind); - JSObject *obj = JSObject::create(cx, kind, GetInitialHeap(GenericObject, &CallClass), - shape, type, slots); + gc::InitialHeap heap = script->treatAsRunOnce ? gc::TenuredHeap : gc::DefaultHeap; + JSObject *obj = JSObject::create(cx, kind, heap, shape, type, slots); if (!obj) return NULL; + + if (script->treatAsRunOnce) { + RootedObject nobj(cx, obj); + if (!JSObject::setSingletonType(cx, nobj)) + return NULL; + return &nobj->asCall(); + } + return &obj->asCall(); } @@ -199,6 +228,14 @@ CallObject::create(JSContext *cx, HandleScript script, HandleObject enclosing, H callobj->asScope().setEnclosingScope(enclosing); callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee)); + + if (script->treatAsRunOnce) { + Rooted ncallobj(cx, callobj); + if (!JSObject::setSingletonType(cx, ncallobj)) + return NULL; + return ncallobj; + } + return callobj; } @@ -236,8 +273,10 @@ CallObject::createForFunction(JSContext *cx, AbstractFramePtr frame) return NULL; /* Copy in the closed-over formal arguments. */ - for (AliasedFormalIter i(frame.script()); i; i++) - callobj->setAliasedVar(i, frame.unaliasedFormal(i.frameIndex(), DONT_CHECK_ALIASING)); + for (AliasedFormalIter i(frame.script()); i; i++) { + callobj->setAliasedVar(cx, i, i->name(), + frame.unaliasedFormal(i.frameIndex(), DONT_CHECK_ALIASING)); + } return callobj; } diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index 4156bc91dac..02eb9d0e264 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -59,7 +59,7 @@ class StaticScopeIter bool onNamedLambda; public: - explicit StaticScopeIter(JSContext *cx, HandleObject obj); + explicit StaticScopeIter(JSContext *cx, JSObject *obj); bool done() const; void operator++(int); @@ -106,6 +106,10 @@ ScopeCoordinateToStaticScopeShape(JSContext *cx, JSScript *script, jsbytecode *p extern PropertyName * ScopeCoordinateName(JSContext *cx, JSScript *script, jsbytecode *pc); +/* Return the function script accessed by the given ALIASEDVAR op, or NULL. */ +extern JSScript * +ScopeCoordinateFunctionScript(JSContext *cx, JSScript *script, jsbytecode *pc); + /*****************************************************************************/ /* @@ -167,7 +171,7 @@ class ScopeObject : public JSObject * take a ScopeCoordinate instead of just the slot index. */ inline const Value &aliasedVar(ScopeCoordinate sc); - inline void setAliasedVar(ScopeCoordinate sc, const Value &v); + inline void setAliasedVar(JSContext *cx, ScopeCoordinate sc, PropertyName *name, const Value &v); /* For jit access. */ static inline size_t offsetOfEnclosingScope(); @@ -187,7 +191,7 @@ class CallObject : public ScopeObject public: /* These functions are internal and are exposed only for JITs. */ static CallObject * - create(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots); + create(JSContext *cx, HandleScript script, HandleShape shape, HandleTypeObject type, HeapSlot *slots); static CallObject * createTemplateObject(JSContext *cx, HandleScript script, gc::InitialHeap heap); @@ -210,7 +214,7 @@ class CallObject : public ScopeObject /* Get/set the aliased variable referred to by 'bi'. */ inline const Value &aliasedVar(AliasedFormalIter fi); - inline void setAliasedVar(AliasedFormalIter fi, const Value &v); + inline void setAliasedVar(JSContext *cx, AliasedFormalIter fi, PropertyName *name, const Value &v); /* For jit access. */ static inline size_t offsetOfCallee(); diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 3d105d1372a..595d4146aa4 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -26,7 +26,7 @@ namespace js { * and saved versions. If deserialization fails, the data should be * invalidated if possible. */ -static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 146); +static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 147); class XDRBuffer { public: