From 3eeee69bed7c2596bbd6f49441dd35fd8d3f0325 Mon Sep 17 00:00:00 2001 From: Kannan Vijayan Date: Thu, 18 Apr 2013 18:00:23 -0400 Subject: [PATCH] Bug 861596 - Add Baseline stubs to handle GetProp(length) and GetElem(int32) operations on arguments objects. r=bhackett --- js/src/ion/BaselineIC.cpp | 271 +++++++++++++++++++++++++++- js/src/ion/BaselineIC.h | 93 ++++++++++ js/src/ion/arm/MacroAssembler-arm.h | 11 ++ js/src/ion/x64/MacroAssembler-x64.h | 17 ++ js/src/ion/x86/MacroAssembler-x86.h | 18 ++ js/src/vm/ArgumentsObject.h | 5 + 6 files changed, 414 insertions(+), 1 deletion(-) diff --git a/js/src/ion/BaselineIC.cpp b/js/src/ion/BaselineIC.cpp index 4403911b76a8..4455609df9fa 100644 --- a/js/src/ion/BaselineIC.cpp +++ b/js/src/ion/BaselineIC.cpp @@ -3346,6 +3346,18 @@ TypedArrayGetElemStubExists(ICGetElem_Fallback *stub, HandleObject obj) return false; } +static bool +ArgumentsGetElemStubExists(ICGetElem_Fallback *stub, ICGetElem_Arguments::Which which) +{ + for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { + if (!iter->isGetElem_Arguments()) + continue; + if (iter->toGetElem_Arguments()->which() == which) + return true; + } + return false; +} + static bool TryAttachNativeGetElemStub(JSContext *cx, HandleScript script, ICGetElem_Fallback *stub, HandleObject obj, @@ -3417,11 +3429,43 @@ TryAttachGetElemStub(JSContext *cx, HandleScript script, ICGetElem_Fallback *stu return true; } + if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS) && rhs.isInt32() && + !ArgumentsGetElemStubExists(stub, ICGetElem_Arguments::Magic)) + { + IonSpew(IonSpew_BaselineIC, " Generating GetElem(MagicArgs[Int32]) stub"); + ICGetElem_Arguments::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), + ICGetElem_Arguments::Magic); + ICStub *argsStub = compiler.getStub(compiler.getStubSpace(script)); + if (!argsStub) + return false; + + stub->addNewStub(argsStub); + return true; + } + // Otherwise, GetElem is only optimized on objects. if (!lhs.isObject()) return true; RootedObject obj(cx, &lhs.toObject()); + // Check for ArgumentsObj[int] accesses + if (obj->isArguments() && rhs.isInt32()) { + ICGetElem_Arguments::Which which = ICGetElem_Arguments::Normal; + if (obj->isStrictArguments()) + which = ICGetElem_Arguments::Strict; + if (!ArgumentsGetElemStubExists(stub, which)) { + IonSpew(IonSpew_BaselineIC, " Generating GetElem(ArgsObj[Int32]) stub"); + ICGetElem_Arguments::Compiler compiler( + cx, stub->fallbackMonitorStub()->firstMonitorStub(), which); + ICStub *argsStub = compiler.getStub(compiler.getStubSpace(script)); + if (!argsStub) + return false; + + stub->addNewStub(argsStub); + return true; + } + } + if (obj->isNative()) { // Check for NativeObject[int] dense accesses. if (rhs.isInt32()) { @@ -3743,6 +3787,138 @@ ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler &masm) return true; } +// +// GetEelem_Arguments +// +bool +ICGetElem_Arguments::Compiler::generateStubCode(MacroAssembler &masm) +{ + Label failure; + if (which_ == ICGetElem_Arguments::Magic) { + // Ensure that this is a magic arguments value. + masm.branchTestMagicValue(Assembler::NotEqual, R0, JS_OPTIMIZED_ARGUMENTS, &failure); + + // Ensure that frame has not loaded different arguments object since. + masm.branchTest32(Assembler::NonZero, + Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()), + Imm32(BaselineFrame::HAS_ARGS_OBJ), + &failure); + + // Ensure that index is an integer. + masm.branchTestInt32(Assembler::NotEqual, R1, &failure); + Register idx = masm.extractInt32(R1, ExtractTemp1); + + GeneralRegisterSet regs(availableGeneralRegs(2)); + Register scratch = regs.takeAny(); + + // Load num actual arguments + Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs()); + masm.loadPtr(actualArgs, scratch); + + // Ensure idx < argc + masm.branch32(Assembler::AboveOrEqual, idx, scratch, &failure); + + // Load argval + JS_ASSERT(sizeof(Value) == 8); + masm.movePtr(BaselineFrameReg, scratch); + masm.addPtr(Imm32(BaselineFrame::offsetOfArg(0)), scratch); + BaseIndex element(scratch, idx, TimesEight); + masm.loadValue(element, R0); + + // Enter type monitor IC to type-check result. + EmitEnterTypeMonitorIC(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; + } + + JS_ASSERT(which_ == ICGetElem_Arguments::Strict || + which_ == ICGetElem_Arguments::Normal); + + bool isStrict = which_ == ICGetElem_Arguments::Strict; + Class *clasp = isStrict ? &StrictArgumentsObjectClass : &NormalArgumentsObjectClass; + + GeneralRegisterSet regs(availableGeneralRegs(2)); + Register scratchReg = regs.takeAny(); + + // Guard on input being an arguments object. + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + Register objReg = masm.extractObject(R0, ExtractTemp0); + masm.branchTestObjClass(Assembler::NotEqual, objReg, scratchReg, clasp, &failure); + + // Guard on index being int32 + masm.branchTestInt32(Assembler::NotEqual, R1, &failure); + Register idxReg = masm.extractInt32(R1, ExtractTemp1); + + // Get initial ArgsObj length value. + masm.unboxInt32(Address(objReg, ArgumentsObject::getInitialLengthSlotOffset()), scratchReg); + + // Test if length has been overridden. + masm.branchTest32(Assembler::NonZero, + scratchReg, + Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), + &failure); + + // Length has not been overridden, ensure that R1 is an integer and is <= length. + masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratchReg); + masm.branch32(Assembler::AboveOrEqual, idxReg, scratchReg, &failure); + + // Length check succeeded, now check the correct bit. We clobber potential type regs + // now. Inputs will have to be reconstructed if we fail after this point, but that's + // unlikely. + Label failureReconstructInputs; + regs = availableGeneralRegs(0); + if (regs.has(objReg)) + regs.take(objReg); + if (regs.has(idxReg)) + regs.take(idxReg); + if (regs.has(scratchReg)) + regs.take(scratchReg); + Register argData = regs.takeAny(); + Register tempReg = regs.takeAny(); + + // Load ArgumentsData + masm.loadPrivate(Address(objReg, ArgumentsObject::getDataSlotOffset()), argData); + + // Load deletedBits bitArray pointer into scratchReg + masm.loadPtr(Address(argData, offsetof(ArgumentsData, deletedBits)), scratchReg); + + // In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord) + masm.movePtr(idxReg, tempReg); + masm.rshiftPtr(Imm32(JS_BITS_PER_WORD_LOG2), tempReg); + masm.loadPtr(BaseIndex(scratchReg, tempReg, ScaleFromElemWidth(sizeof(size_t))), scratchReg); + + // Don't bother testing specific bit, if any bit is set in the word, fail. + masm.branchPtr(Assembler::NotEqual, scratchReg, ImmWord((size_t)0), &failureReconstructInputs); + + // Load the value. use scratchReg and tempReg to form a ValueOperand to load into. + masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), argData); + regs.add(scratchReg); + regs.add(tempReg); + ValueOperand tempVal = regs.takeAnyValue(); + masm.loadValue(BaseIndex(argData, idxReg, ScaleFromElemWidth(sizeof(Value))), tempVal); + + // Makesure that this is not a FORWARD_TO_CALL_SLOT magic value. + masm.branchTestMagic(Assembler::Equal, tempVal, &failureReconstructInputs); + + // Everything checked out, return value. + masm.moveValue(tempVal, R0); + + // Type-check result + EmitEnterTypeMonitorIC(masm); + + // Failed, but inputs are deconstructed into object and int, and need to be + // reconstructed into values. + masm.bind(&failureReconstructInputs); + masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0); + masm.tagValue(JSVAL_TYPE_INT32, idxReg, R1); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + // // SetElem_Fallback // @@ -4849,6 +5025,18 @@ TryAttachLengthStub(JSContext *cx, HandleScript script, ICGetProp_Fallback *stub return true; } + if (val.isMagic(JS_OPTIMIZED_ARGUMENTS) && res.isInt32()) { + IonSpew(IonSpew_BaselineIC, " Generating GetProp(MagicArgs.length) stub"); + ICGetProp_ArgumentsLength::Compiler compiler(cx, ICGetProp_ArgumentsLength::Magic); + ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); + if (!newStub) + return false; + + *attached = true; + stub->addNewStub(newStub); + return true; + } + if (!val.isObject()) return true; @@ -4878,6 +5066,22 @@ TryAttachLengthStub(JSContext *cx, HandleScript script, ICGetProp_Fallback *stub return true; } + if (obj->isArguments() && res.isInt32()) { + IonSpew(IonSpew_BaselineIC, " Generating GetProp(ArgsObj.length %s) stub", + obj->isStrictArguments() ? "Strict" : "Normal"); + ICGetProp_ArgumentsLength::Which which = ICGetProp_ArgumentsLength::Normal; + if (obj->isStrictArguments()) + which = ICGetProp_ArgumentsLength::Strict; + ICGetProp_ArgumentsLength::Compiler compiler(cx, which); + ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); + if (!newStub) + return false; + + *attached = true; + stub->addNewStub(newStub); + return true; + } + return true; } @@ -5032,9 +5236,18 @@ DoGetPropFallback(JSContext *cx, BaselineFrame *frame, ICGetProp_Fallback *stub, if (op == JSOP_LENGTH && val.isMagic(JS_OPTIMIZED_ARGUMENTS)) { // Handle arguments.length access. if (IsOptimizedArguments(frame, val.address())) { - // TODO: attach optimized stub. res.setInt32(frame->numActualArgs()); + + // Monitor result types::TypeScript::Monitor(cx, script, pc, res); + if (!stub->addMonitorStubForValue(cx, script, res)) + return false; + + bool attached = false; + if (!TryAttachLengthStub(cx, script, stub, val, res, &attached)) + return false; + JS_ASSERT(attached); + return true; } } @@ -5567,6 +5780,62 @@ ICGetProp_CallListBaseNative::Compiler::generateStubCode(MacroAssembler &masm) return true; } +bool +ICGetProp_ArgumentsLength::Compiler::generateStubCode(MacroAssembler &masm) +{ + Label failure; + if (which_ == ICGetProp_ArgumentsLength::Magic) { + // Ensure that this is lazy arguments. + masm.branchTestMagicValue(Assembler::NotEqual, R0, JS_OPTIMIZED_ARGUMENTS, &failure); + + // Ensure that frame has not loaded different arguments object since. + masm.branchTest32(Assembler::NonZero, + Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()), + Imm32(BaselineFrame::HAS_ARGS_OBJ), + &failure); + + Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs()); + masm.loadPtr(actualArgs, R0.scratchReg()); + masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0); + EmitReturnFromIC(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; + } + JS_ASSERT(which_ == ICGetProp_ArgumentsLength::Strict || + which_ == ICGetProp_ArgumentsLength::Normal); + + bool isStrict = which_ == ICGetProp_ArgumentsLength::Strict; + Class *clasp = isStrict ? &StrictArgumentsObjectClass : &NormalArgumentsObjectClass; + + Register scratchReg = R1.scratchReg(); + + // Guard on input being an arguments object. + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + Register objReg = masm.extractObject(R0, ExtractTemp0); + masm.branchTestObjClass(Assembler::NotEqual, objReg, scratchReg, clasp, &failure); + + // Get initial length value. + masm.unboxInt32(Address(objReg, ArgumentsObject::getInitialLengthSlotOffset()), scratchReg); + + // Test if length has been overridden. + masm.branchTest32(Assembler::NonZero, + scratchReg, + Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), + &failure); + + // Nope, shift out arguments length and return it. + // No need to type monitor because this stub always returns Int32. + masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratchReg); + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + EmitReturnFromIC(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + // // SetProp_Fallback // diff --git a/js/src/ion/BaselineIC.h b/js/src/ion/BaselineIC.h index 461628faf47e..38b7b40a4808 100644 --- a/js/src/ion/BaselineIC.h +++ b/js/src/ion/BaselineIC.h @@ -331,6 +331,7 @@ class ICEntry _(GetElem_String) \ _(GetElem_Dense) \ _(GetElem_TypedArray) \ + _(GetElem_Arguments) \ \ _(SetElem_Fallback) \ _(SetElem_Dense) \ @@ -364,6 +365,7 @@ class ICEntry _(GetProp_CallScripted) \ _(GetProp_CallNative) \ _(GetProp_CallListBaseNative)\ + _(GetProp_ArgumentsLength) \ \ _(SetProp_Fallback) \ _(SetProp_Native) \ @@ -3034,6 +3036,56 @@ class ICGetElem_TypedArray : public ICStub }; }; +class ICGetElem_Arguments : public ICMonitoredStub +{ + friend class ICStubSpace; + public: + enum Which { Normal, Strict, Magic }; + + private: + ICGetElem_Arguments(IonCode *stubCode, ICStub *firstMonitorStub, Which which) + : ICMonitoredStub(ICStub::GetElem_Arguments, stubCode, firstMonitorStub) + { + extra_ = static_cast(which); + } + + public: + static inline ICGetElem_Arguments *New(ICStubSpace *space, IonCode *code, + ICStub *firstMonitorStub, Which which) + { + if (!code) + return NULL; + return space->allocate(code, firstMonitorStub, which); + } + + Which which() const { + return static_cast(extra_); + } + + class Compiler : public ICStubCompiler { + ICStub *firstMonitorStub_; + Which which_; + + protected: + bool generateStubCode(MacroAssembler &masm); + + virtual int32_t getKey() const { + return static_cast(kind) | (static_cast(which_) << 16); + } + + public: + Compiler(JSContext *cx, ICStub *firstMonitorStub, Which which) + : ICStubCompiler(cx, ICStub::GetElem_Arguments), + firstMonitorStub_(firstMonitorStub), + which_(which) + {} + + ICStub *getStub(ICStubSpace *space) { + return ICGetElem_Arguments::New(space, getStubCode(), firstMonitorStub_, which_); + } + }; +}; + // SetElem // JSOP_SETELEM // JSOP_INITELEM @@ -4219,6 +4271,47 @@ class ICGetProp_CallListBaseNative : public ICMonitoredStub }; }; +class ICGetProp_ArgumentsLength : public ICStub +{ + friend class ICStubSpace; + public: + enum Which { Normal, Strict, Magic }; + + protected: + ICGetProp_ArgumentsLength(IonCode *stubCode) + : ICStub(ICStub::GetProp_ArgumentsLength, stubCode) + { } + + public: + static inline ICGetProp_ArgumentsLength *New(ICStubSpace *space, IonCode *code) + { + if (!code) + return NULL; + return space->allocate(code); + } + + class Compiler : public ICStubCompiler { + protected: + Which which_; + + bool generateStubCode(MacroAssembler &masm); + + virtual int32_t getKey() const { + return static_cast(kind) | (static_cast(which_) << 16); + } + + public: + Compiler(JSContext *cx, Which which) + : ICStubCompiler(cx, ICStub::GetProp_ArgumentsLength), + which_(which) + {} + + ICStub *getStub(ICStubSpace *space) { + return ICGetProp_ArgumentsLength::New(space, getStubCode()); + } + }; +}; + // SetProp // JSOP_SETPROP // JSOP_SETNAME diff --git a/js/src/ion/arm/MacroAssembler-arm.h b/js/src/ion/arm/MacroAssembler-arm.h index 18b258b750cc..2f8551df5912 100644 --- a/js/src/ion/arm/MacroAssembler-arm.h +++ b/js/src/ion/arm/MacroAssembler-arm.h @@ -804,6 +804,17 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM cond = testMagic(cond, t); ma_b(label, cond); } + void branchTestMagicValue(Condition cond, const ValueOperand &val, JSWhyMagic why, + Label *label) { + JS_ASSERT(cond == Equal || cond == NotEqual); + // Test for magic + Label notmagic; + Condition testCond = testMagic(cond, val); + ma_b(¬magic, InvertCondition(testCond)); + // Test magic value + branch32(cond, val.payloadReg(), Imm32(static_cast(why)), label); + bind(¬magic); + } template void branchTestBooleanTruthy(bool b, const T & t, Label *label) { Condition c = testBooleanTruthy(b, t); diff --git a/js/src/ion/x64/MacroAssembler-x64.h b/js/src/ion/x64/MacroAssembler-x64.h index c7b8eecba0aa..112b3df66b1b 100644 --- a/js/src/ion/x64/MacroAssembler-x64.h +++ b/js/src/ion/x64/MacroAssembler-x64.h @@ -705,6 +705,19 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared cond = testMagic(cond, t); j(cond, label); } + void branchTestMagicValue(Condition cond, const ValueOperand &val, JSWhyMagic why, + Label *label) + { + JS_ASSERT(cond == Equal || cond == NotEqual); + // Test for magic + Label notmagic; + Condition testCond = testMagic(cond, val); + j(InvertCondition(testCond), ¬magic); + // Test magic value + unboxMagic(val, ScratchReg); + branch32(cond, ScratchReg, Imm32(static_cast(why)), label); + bind(¬magic); + } Condition testMagic(Condition cond, const ValueOperand &src) { splitTag(src, ScratchReg); return testMagic(cond, ScratchReg); @@ -768,6 +781,10 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared unboxBoolean(Operand(src), dest); } + void unboxMagic(const ValueOperand &src, const Register &dest) { + movl(Operand(src.valueReg()), dest); + } + void unboxDouble(const ValueOperand &src, const FloatRegister &dest) { movqsd(src.valueReg(), dest); } diff --git a/js/src/ion/x86/MacroAssembler-x86.h b/js/src/ion/x86/MacroAssembler-x86.h index 18f95d8bb89d..353ea072052d 100644 --- a/js/src/ion/x86/MacroAssembler-x86.h +++ b/js/src/ion/x86/MacroAssembler-x86.h @@ -607,6 +607,24 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared cond = testMagic(cond, t); j(cond, label); } + void branchTestMagicValue(Condition cond, const ValueOperand &val, JSWhyMagic why, + Label *label) + { + JS_ASSERT(cond == Equal || cond == NotEqual); + if (cond == Equal) { + // Test for magic + Label notmagic; + Condition testCond = testMagic(Equal, val); + j(InvertCondition(testCond), ¬magic); + // Test magic value + branch32(NotEqual, val.payloadReg(), Imm32(static_cast(why)), label); + bind(¬magic); + } else { + Condition testCond = testMagic(NotEqual, val); + j(testCond, label); + branch32(NotEqual, val.payloadReg(), Imm32(static_cast(why)), label); + } + } // Note: this function clobbers the source register. void boxDouble(const FloatRegister &src, const ValueOperand &dest) { diff --git a/js/src/vm/ArgumentsObject.h b/js/src/vm/ArgumentsObject.h index 7e8b90c64f2b..19dac2428b78 100644 --- a/js/src/vm/ArgumentsObject.h +++ b/js/src/vm/ArgumentsObject.h @@ -99,9 +99,11 @@ class ArgumentsObject : public JSObject static const uint32_t DATA_SLOT = 1; static const uint32_t MAYBE_CALL_SLOT = 2; + public: static const uint32_t LENGTH_OVERRIDDEN_BIT = 0x1; static const uint32_t PACKED_BITS_COUNT = 1; + protected: template static ArgumentsObject *create(JSContext *cx, HandleScript script, HandleFunction callee, unsigned numActuals, CopyArgs ©); @@ -204,6 +206,9 @@ class ArgumentsObject : public JSObject static size_t getDataSlotOffset() { return getFixedSlotOffset(DATA_SLOT); } + static size_t getInitialLengthSlotOffset() { + return getFixedSlotOffset(INITIAL_LENGTH_SLOT); + } static void MaybeForwardToCallObject(AbstractFramePtr frame, JSObject *obj, ArgumentsData *data); #if defined(JS_ION)