michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef vm_ArgumentsObject_h michael@0: #define vm_ArgumentsObject_h michael@0: michael@0: #include "mozilla/MemoryReporting.h" michael@0: michael@0: #include "jsobj.h" michael@0: michael@0: #include "gc/Barrier.h" michael@0: michael@0: namespace js { michael@0: michael@0: class AbstractFramePtr; michael@0: class ScriptFrameIter; michael@0: michael@0: namespace jit { michael@0: class IonJSFrameLayout; michael@0: } michael@0: michael@0: /* michael@0: * ArgumentsData stores the initial indexed arguments provided to the michael@0: * corresponding and that function itself. It is used to store arguments[i] michael@0: * and arguments.callee -- up until the corresponding property is modified, michael@0: * when the relevant value is flagged to memorialize the modification. michael@0: */ michael@0: struct ArgumentsData michael@0: { michael@0: /* michael@0: * numArgs = Max(numFormalArgs, numActualArgs) michael@0: * The array 'args' has numArgs elements. michael@0: */ michael@0: unsigned numArgs; michael@0: michael@0: /* michael@0: * arguments.callee, or MagicValue(JS_OVERWRITTEN_CALLEE) if michael@0: * arguments.callee has been modified. michael@0: */ michael@0: HeapValue callee; michael@0: michael@0: /* The script for the function containing this arguments object. */ michael@0: JSScript *script; michael@0: michael@0: /* michael@0: * Pointer to an array of bits indicating, for every argument in 'slots', michael@0: * whether the element has been deleted. See isElementDeleted comment. michael@0: */ michael@0: size_t *deletedBits; michael@0: michael@0: /* michael@0: * This array holds either the current argument value or the magic michael@0: * forwarding value. The latter means that the function has both a michael@0: * CallObject and an ArgumentsObject AND the particular formal variable is michael@0: * aliased by the CallObject. In such cases, the CallObject holds the michael@0: * canonical value so any element access to the arguments object should load michael@0: * the value out of the CallObject (which is pointed to by MAYBE_CALL_SLOT). michael@0: */ michael@0: HeapValue args[1]; michael@0: michael@0: /* For jit use: */ michael@0: static ptrdiff_t offsetOfArgs() { return offsetof(ArgumentsData, args); } michael@0: }; michael@0: michael@0: // Maximum supported value of arguments.length. This bounds the maximum michael@0: // number of arguments that can be supplied to Function.prototype.apply. michael@0: // This value also bounds the number of elements parsed in an array michael@0: // initialiser. michael@0: static const unsigned ARGS_LENGTH_MAX = 500 * 1000; michael@0: michael@0: /* michael@0: * ArgumentsObject instances represent |arguments| objects created to store michael@0: * function arguments when a function is called. It's expensive to create such michael@0: * objects if they're never used, so they're only created when they are michael@0: * potentially used. michael@0: * michael@0: * Arguments objects are complicated because, for non-strict mode code, they michael@0: * must alias any named arguments which were provided to the function. Gnarly michael@0: * example: michael@0: * michael@0: * function f(a, b, c, d) michael@0: * { michael@0: * arguments[0] = "seta"; michael@0: * assertEq(a, "seta"); michael@0: * b = "setb"; michael@0: * assertEq(arguments[1], "setb"); michael@0: * c = "setc"; michael@0: * assertEq(arguments[2], undefined); michael@0: * arguments[3] = "setd"; michael@0: * assertEq(d, undefined); michael@0: * } michael@0: * f("arga", "argb"); michael@0: * michael@0: * ES5's strict mode behaves more sanely, and named arguments don't alias michael@0: * elements of an arguments object. michael@0: * michael@0: * ArgumentsObject instances use the following reserved slots: michael@0: * michael@0: * INITIAL_LENGTH_SLOT michael@0: * Stores the initial value of arguments.length, plus a bit indicating michael@0: * whether arguments.length has been modified. Use initialLength() and michael@0: * hasOverriddenLength() to access these values. If arguments.length has michael@0: * been modified, then the current value of arguments.length is stored in michael@0: * another slot associated with a new property. michael@0: * DATA_SLOT michael@0: * Stores an ArgumentsData*, described above. michael@0: */ michael@0: class ArgumentsObject : public JSObject michael@0: { michael@0: protected: michael@0: static const uint32_t INITIAL_LENGTH_SLOT = 0; michael@0: static const uint32_t DATA_SLOT = 1; michael@0: static const uint32_t MAYBE_CALL_SLOT = 2; michael@0: michael@0: public: michael@0: static const uint32_t LENGTH_OVERRIDDEN_BIT = 0x1; michael@0: static const uint32_t PACKED_BITS_COUNT = 1; michael@0: michael@0: protected: michael@0: template michael@0: static ArgumentsObject *create(JSContext *cx, HandleScript script, HandleFunction callee, michael@0: unsigned numActuals, CopyArgs ©); michael@0: michael@0: ArgumentsData *data() const { michael@0: return reinterpret_cast(getFixedSlot(DATA_SLOT).toPrivate()); michael@0: } michael@0: michael@0: public: michael@0: static const uint32_t RESERVED_SLOTS = 3; michael@0: static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND; michael@0: michael@0: /* Create an arguments object for a frame that is expecting them. */ michael@0: static ArgumentsObject *createExpected(JSContext *cx, AbstractFramePtr frame); michael@0: michael@0: /* michael@0: * Purposefully disconnect the returned arguments object from the frame michael@0: * by always creating a new copy that does not alias formal parameters. michael@0: * This allows function-local analysis to determine that formals are michael@0: * not aliased and generally simplifies arguments objects. michael@0: */ michael@0: static ArgumentsObject *createUnexpected(JSContext *cx, ScriptFrameIter &iter); michael@0: static ArgumentsObject *createUnexpected(JSContext *cx, AbstractFramePtr frame); michael@0: #if defined(JS_ION) michael@0: static ArgumentsObject *createForIon(JSContext *cx, jit::IonJSFrameLayout *frame, michael@0: HandleObject scopeChain); michael@0: #endif michael@0: michael@0: /* michael@0: * Return the initial length of the arguments. This may differ from the michael@0: * current value of arguments.length! michael@0: */ michael@0: uint32_t initialLength() const { michael@0: uint32_t argc = uint32_t(getFixedSlot(INITIAL_LENGTH_SLOT).toInt32()) >> PACKED_BITS_COUNT; michael@0: JS_ASSERT(argc <= ARGS_LENGTH_MAX); michael@0: return argc; michael@0: } michael@0: michael@0: /* The script for the function containing this arguments object. */ michael@0: JSScript *containingScript() const { michael@0: return data()->script; michael@0: } michael@0: michael@0: /* True iff arguments.length has been assigned or its attributes changed. */ michael@0: bool hasOverriddenLength() const { michael@0: const Value &v = getFixedSlot(INITIAL_LENGTH_SLOT); michael@0: return v.toInt32() & LENGTH_OVERRIDDEN_BIT; michael@0: } michael@0: michael@0: void markLengthOverridden() { michael@0: uint32_t v = getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | LENGTH_OVERRIDDEN_BIT; michael@0: setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v)); michael@0: } michael@0: michael@0: /* michael@0: * Because the arguments object is a real object, its elements may be michael@0: * deleted. This is implemented by setting a 'deleted' flag for the arg michael@0: * which is read by argument object resolve and getter/setter hooks. michael@0: * michael@0: * NB: an element, once deleted, stays deleted. Thus: michael@0: * michael@0: * function f(x) { delete arguments[0]; arguments[0] = 42; return x } michael@0: * assertEq(f(1), 1); michael@0: * michael@0: * This works because, once a property is deleted from an arguments object, michael@0: * it gets regular properties with regular getters/setters that don't alias michael@0: * ArgumentsData::slots. michael@0: */ michael@0: bool isElementDeleted(uint32_t i) const { michael@0: JS_ASSERT(i < data()->numArgs); michael@0: if (i >= initialLength()) michael@0: return false; michael@0: return IsBitArrayElementSet(data()->deletedBits, initialLength(), i); michael@0: } michael@0: michael@0: bool isAnyElementDeleted() const { michael@0: return IsAnyBitArrayElementSet(data()->deletedBits, initialLength()); michael@0: } michael@0: michael@0: void markElementDeleted(uint32_t i) { michael@0: SetBitArrayElement(data()->deletedBits, initialLength(), i); michael@0: } michael@0: michael@0: /* michael@0: * An ArgumentsObject serves two roles: michael@0: * - a real object, accessed through regular object operations, e.g.., michael@0: * JSObject::getElement corresponding to 'arguments[i]'; michael@0: * - a VM-internal data structure, storing the value of arguments (formal michael@0: * and actual) that are accessed directly by the VM when a reading the michael@0: * value of a formal parameter. michael@0: * There are two ways to access the ArgumentsData::args corresponding to michael@0: * these two use cases: michael@0: * - object access should use elements(i) which will take care of michael@0: * forwarding when the value is the magic forwarding value; michael@0: * - VM argument access should use arg(i) which will assert that the michael@0: * value is not the magic forwarding value (since, if such forwarding was michael@0: * needed, the frontend should have emitted JSOP_GETALIASEDVAR). michael@0: */ michael@0: const Value &element(uint32_t i) const; michael@0: michael@0: inline void setElement(JSContext *cx, uint32_t i, const Value &v); michael@0: michael@0: const Value &arg(unsigned i) const { michael@0: JS_ASSERT(i < data()->numArgs); michael@0: const Value &v = data()->args[i]; michael@0: JS_ASSERT(!v.isMagic()); michael@0: return v; michael@0: } michael@0: michael@0: void setArg(unsigned i, const Value &v) { michael@0: JS_ASSERT(i < data()->numArgs); michael@0: HeapValue &lhs = data()->args[i]; michael@0: JS_ASSERT(!lhs.isMagic()); michael@0: lhs = v; michael@0: } michael@0: michael@0: /* michael@0: * Attempt to speedily and efficiently access the i-th element of this michael@0: * arguments object. Return true if the element was speedily returned. michael@0: * Return false if the element must be looked up more slowly using michael@0: * getProperty or some similar method. The second overload copies the michael@0: * elements [start, start + count) into the locations starting at 'vp'. michael@0: * michael@0: * NB: Returning false does not indicate error! michael@0: */ michael@0: bool maybeGetElement(uint32_t i, MutableHandleValue vp) { michael@0: if (i >= initialLength() || isElementDeleted(i)) michael@0: return false; michael@0: vp.set(element(i)); michael@0: return true; michael@0: } michael@0: michael@0: inline bool maybeGetElements(uint32_t start, uint32_t count, js::Value *vp); michael@0: michael@0: /* michael@0: * Measures things hanging off this ArgumentsObject that are counted by the michael@0: * |miscSize| argument in JSObject::sizeOfExcludingThis(). michael@0: */ michael@0: size_t sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const { michael@0: return mallocSizeOf(data()); michael@0: } michael@0: michael@0: static void finalize(FreeOp *fop, JSObject *obj); michael@0: static void trace(JSTracer *trc, JSObject *obj); michael@0: michael@0: /* For jit use: */ michael@0: static size_t getDataSlotOffset() { michael@0: return getFixedSlotOffset(DATA_SLOT); michael@0: } michael@0: static size_t getInitialLengthSlotOffset() { michael@0: return getFixedSlotOffset(INITIAL_LENGTH_SLOT); michael@0: } michael@0: michael@0: static void MaybeForwardToCallObject(AbstractFramePtr frame, JSObject *obj, ArgumentsData *data); michael@0: #if defined(JS_ION) michael@0: static void MaybeForwardToCallObject(jit::IonJSFrameLayout *frame, HandleObject callObj, michael@0: JSObject *obj, ArgumentsData *data); michael@0: #endif michael@0: }; michael@0: michael@0: class NormalArgumentsObject : public ArgumentsObject michael@0: { michael@0: public: michael@0: static const Class class_; michael@0: michael@0: /* michael@0: * Stores arguments.callee, or MagicValue(JS_ARGS_HOLE) if the callee has michael@0: * been cleared. michael@0: */ michael@0: const js::Value &callee() const { michael@0: return data()->callee; michael@0: } michael@0: michael@0: /* Clear the location storing arguments.callee's initial value. */ michael@0: void clearCallee() { michael@0: data()->callee.set(zone(), MagicValue(JS_OVERWRITTEN_CALLEE)); michael@0: } michael@0: }; michael@0: michael@0: class StrictArgumentsObject : public ArgumentsObject michael@0: { michael@0: public: michael@0: static const Class class_; michael@0: }; michael@0: michael@0: } // namespace js michael@0: michael@0: template<> michael@0: inline bool michael@0: JSObject::is() const michael@0: { michael@0: return is() || is(); michael@0: } michael@0: michael@0: #endif /* vm_ArgumentsObject_h */