Backed out changeset 8f976ed899d7 (bug 1847231) for causing bc failures on browser_se...
[gecko.git] / js / src / vm / ArgumentsObject.h
blob93ad790f885e2f470c5382e0db666687d25d2243
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef vm_ArgumentsObject_h
8 #define vm_ArgumentsObject_h
10 #include "mozilla/MemoryReporting.h"
12 #include "gc/Barrier.h"
13 #include "util/BitArray.h"
14 #include "vm/NativeObject.h"
16 namespace js {
18 class AbstractFramePtr;
19 class ArgumentsObject;
20 class ScriptFrameIter;
22 namespace jit {
23 class JitFrameLayout;
24 } // namespace jit
26 // RareArgumentsData stores the deleted-elements bits for an arguments object.
27 // Because |delete arguments[i]| is uncommon, we allocate this data the first
28 // time an element is deleted.
29 class RareArgumentsData {
30 // Pointer to an array of bits indicating, for every argument in
31 // [0, initialLength) whether the element has been deleted. See
32 // ArgumentsObject::isElementDeleted comment.
33 size_t deletedBits_[1];
35 RareArgumentsData() = default;
36 RareArgumentsData(const RareArgumentsData&) = delete;
37 void operator=(const RareArgumentsData&) = delete;
39 public:
40 static RareArgumentsData* create(JSContext* cx, ArgumentsObject* obj);
41 static size_t bytesRequired(size_t numActuals);
43 bool isElementDeleted(size_t len, size_t i) const {
44 MOZ_ASSERT(i < len);
45 return IsBitArrayElementSet(deletedBits_, len, i);
47 void markElementDeleted(size_t len, size_t i) {
48 MOZ_ASSERT(i < len);
49 SetBitArrayElement(deletedBits_, len, i);
53 // ArgumentsData stores the initial indexed arguments provided to a function
54 // call. It is used to store arguments[i] -- up until the corresponding
55 // property is modified, when the relevant value is flagged to memorialize the
56 // modification.
57 struct ArgumentsData {
59 * numArgs = std::max(numFormalArgs, numActualArgs)
60 * The array 'args' has numArgs elements.
62 uint32_t numArgs;
64 RareArgumentsData* rareData;
67 * This array holds either the current argument value or the magic
68 * forwarding value. The latter means that the function has both a
69 * CallObject and an ArgumentsObject AND the particular formal variable is
70 * aliased by the CallObject. In such cases, the CallObject holds the
71 * canonical value so any element access to the arguments object should load
72 * the value out of the CallObject (which is pointed to by MAYBE_CALL_SLOT).
74 GCPtr<Value> args[1];
76 /* For jit use: */
77 static ptrdiff_t offsetOfArgs() { return offsetof(ArgumentsData, args); }
79 /* Iterate args. */
80 GCPtr<Value>* begin() { return args; }
81 const GCPtr<Value>* begin() const { return args; }
82 GCPtr<Value>* end() { return args + numArgs; }
83 const GCPtr<Value>* end() const { return args + numArgs; }
85 static size_t bytesRequired(size_t numArgs) {
86 return offsetof(ArgumentsData, args) + numArgs * sizeof(Value);
90 // Maximum supported value of arguments.length. This bounds the
91 // maximum number of arguments that can be supplied to a spread call
92 // or Function.prototype.apply. This value also bounds the number of
93 // elements parsed in an array initializer. NB: keep this in sync
94 // with the copy in builtin/SelfHostingDefines.h.
95 static const unsigned ARGS_LENGTH_MAX = 500 * 1000;
97 // Maximum number of arguments supported in jitcode. This bounds the
98 // maximum number of arguments that can be supplied to a spread call
99 // or Function.prototype.apply without entering the VM. We limit the
100 // number of parameters we can handle to a number that does not risk
101 // us allocating too much stack, notably on Windows where there is a
102 // 4K guard page that has to be touched to extend the stack. The value
103 // "3000" is the size of the guard page minus an arbitrary, but large,
104 // safety margin. See bug 1351278.
105 static const uint32_t JIT_ARGS_LENGTH_MAX = 3000 / sizeof(JS::Value);
107 static_assert(JIT_ARGS_LENGTH_MAX <= ARGS_LENGTH_MAX,
108 "maximum jit arguments should be <= maximum arguments");
111 * [SMDOC] ArgumentsObject
113 * ArgumentsObject instances represent |arguments| objects created to store
114 * function arguments when a function is called. It's expensive to create such
115 * objects if they're never used, so they're only created when they are
116 * potentially used.
118 * Arguments objects are complicated because, for non-strict mode code, they
119 * must alias any named arguments which were provided to the function. Gnarly
120 * example:
122 * function f(a, b, c, d)
124 * arguments[0] = "seta";
125 * assertEq(a, "seta");
126 * b = "setb";
127 * assertEq(arguments[1], "setb");
128 * c = "setc";
129 * assertEq(arguments[2], undefined);
130 * arguments[3] = "setd";
131 * assertEq(d, undefined);
133 * f("arga", "argb");
135 * ES5's strict mode behaves more sanely, and named arguments don't alias
136 * elements of an arguments object.
138 * ArgumentsObject instances use the following reserved slots:
140 * INITIAL_LENGTH_SLOT
141 * Stores the initial value of arguments.length, plus a bit indicating
142 * whether arguments.length and/or arguments[@@iterator] have been
143 * modified. Use initialLength(), hasOverriddenLength(), and
144 * hasOverriddenIterator() to access these values. If arguments.length has
145 * been modified, then the current value of arguments.length is stored in
146 * another slot associated with a new property.
147 * DATA_SLOT
148 * Stores an ArgumentsData*, described above.
149 * MAYBE_CALL_SLOT
150 * Stores the CallObject, if the callee has aliased bindings. See
151 * the ArgumentsData::args comment.
152 * CALLEE_SLOT
153 * Stores the initial arguments.callee. This value can be overridden on
154 * mapped arguments objects, see hasOverriddenCallee.
156 class ArgumentsObject : public NativeObject {
157 public:
158 static const uint32_t INITIAL_LENGTH_SLOT = 0;
159 static const uint32_t DATA_SLOT = 1;
160 static const uint32_t MAYBE_CALL_SLOT = 2;
161 static const uint32_t CALLEE_SLOT = 3;
163 static const uint32_t LENGTH_OVERRIDDEN_BIT = 0x1;
164 static const uint32_t ITERATOR_OVERRIDDEN_BIT = 0x2;
165 static const uint32_t ELEMENT_OVERRIDDEN_BIT = 0x4;
166 static const uint32_t CALLEE_OVERRIDDEN_BIT = 0x8;
167 static const uint32_t FORWARDED_ARGUMENTS_BIT = 0x10;
168 static const uint32_t PACKED_BITS_COUNT = 5;
169 static const uint32_t PACKED_BITS_MASK = (1 << PACKED_BITS_COUNT) - 1;
171 static_assert(ARGS_LENGTH_MAX <= (UINT32_MAX >> PACKED_BITS_COUNT),
172 "Max arguments length must fit in available bits");
174 // Our ability to inline functions that use |arguments| is limited by
175 // the number of registers available to represent Value operands to
176 // CreateInlinedArgumentsObject.
177 #if defined(JS_CODEGEN_X86)
178 static const uint32_t MaxInlinedArgs = 1;
179 #else
180 static const uint32_t MaxInlinedArgs = 3;
181 #endif
183 protected:
184 template <typename CopyArgs>
185 static ArgumentsObject* create(JSContext* cx, HandleFunction callee,
186 unsigned numActuals, CopyArgs& copy);
188 ArgumentsData* data() const {
189 return reinterpret_cast<ArgumentsData*>(
190 getFixedSlot(DATA_SLOT).toPrivate());
193 RareArgumentsData* maybeRareData() const { return data()->rareData; }
195 [[nodiscard]] bool createRareData(JSContext* cx);
197 RareArgumentsData* getOrCreateRareData(JSContext* cx) {
198 if (!data()->rareData && !createRareData(cx)) {
199 return nullptr;
201 return data()->rareData;
204 static bool obj_delProperty(JSContext* cx, HandleObject obj, HandleId id,
205 ObjectOpResult& result);
207 static bool obj_mayResolve(const JSAtomState& names, jsid id, JSObject*);
209 public:
210 static const uint32_t RESERVED_SLOTS = 4;
211 static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT4_BACKGROUND;
213 /* Create an arguments object for a frame that is expecting them. */
214 static ArgumentsObject* createExpected(JSContext* cx, AbstractFramePtr frame);
217 * Purposefully disconnect the returned arguments object from the frame
218 * by always creating a new copy that does not alias formal parameters.
219 * This allows function-local analysis to determine that formals are
220 * not aliased and generally simplifies arguments objects.
222 static ArgumentsObject* createUnexpected(JSContext* cx,
223 ScriptFrameIter& iter);
224 static ArgumentsObject* createUnexpected(JSContext* cx,
225 AbstractFramePtr frame);
227 static ArgumentsObject* createForIon(JSContext* cx,
228 jit::JitFrameLayout* frame,
229 HandleObject scopeChain);
230 static ArgumentsObject* createForInlinedIon(JSContext* cx, Value* args,
231 HandleFunction callee,
232 HandleObject scopeChain,
233 uint32_t numActuals);
234 static ArgumentsObject* createFromValueArray(JSContext* cx,
235 HandleValueArray argsArray,
236 HandleFunction callee,
237 HandleObject scopeChain,
238 uint32_t numActuals);
240 private:
241 template <typename CopyArgs>
242 static ArgumentsObject* finishPure(JSContext* cx, ArgumentsObject* obj,
243 JSFunction* callee, JSObject* callObj,
244 unsigned numActuals, CopyArgs& copy);
246 public:
248 * Allocate ArgumentsData and fill reserved slots after allocating an
249 * ArgumentsObject in Ion code.
251 static ArgumentsObject* finishForIonPure(JSContext* cx,
252 jit::JitFrameLayout* frame,
253 JSObject* scopeChain,
254 ArgumentsObject* obj);
257 * Allocate ArgumentsData for inlined arguments and fill reserved slots after
258 * allocating an ArgumentsObject in Ion code.
260 static ArgumentsObject* finishInlineForIonPure(
261 JSContext* cx, JSObject* rawCallObj, JSFunction* rawCallee, Value* args,
262 uint32_t numActuals, ArgumentsObject* obj);
264 static ArgumentsObject* createTemplateObject(JSContext* cx, bool mapped);
267 * Return the initial length of the arguments. This may differ from the
268 * current value of arguments.length!
270 uint32_t initialLength() const {
271 uint32_t argc = uint32_t(getFixedSlot(INITIAL_LENGTH_SLOT).toInt32()) >>
272 PACKED_BITS_COUNT;
273 MOZ_ASSERT(argc <= ARGS_LENGTH_MAX);
274 return argc;
277 // True iff arguments.length has been assigned or deleted.
278 bool hasOverriddenLength() const {
279 const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT);
280 return v.toInt32() & LENGTH_OVERRIDDEN_BIT;
283 void markLengthOverridden() {
284 uint32_t v =
285 getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | LENGTH_OVERRIDDEN_BIT;
286 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
289 // Create the default "length" property and set LENGTH_OVERRIDDEN_BIT.
290 static bool reifyLength(JSContext* cx, Handle<ArgumentsObject*> obj);
292 // True iff arguments[@@iterator] has been assigned or deleted.
293 bool hasOverriddenIterator() const {
294 const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT);
295 return v.toInt32() & ITERATOR_OVERRIDDEN_BIT;
298 void markIteratorOverridden() {
299 uint32_t v =
300 getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | ITERATOR_OVERRIDDEN_BIT;
301 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
304 // Create the default @@iterator property and set ITERATOR_OVERRIDDEN_BIT.
305 static bool reifyIterator(JSContext* cx, Handle<ArgumentsObject*> obj);
308 * Return the arguments iterator function.
310 static bool getArgumentsIterator(JSContext* cx, MutableHandleValue val);
312 // True iff any element has been assigned or deleted.
313 bool hasOverriddenElement() const {
314 const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT);
315 return v.toInt32() & ELEMENT_OVERRIDDEN_BIT;
318 void markElementOverridden() {
319 uint32_t v =
320 getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | ELEMENT_OVERRIDDEN_BIT;
321 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
324 private:
326 * Because the arguments object is a real object, its elements may be
327 * deleted. This is implemented by setting a 'deleted' flag for the arg
328 * which is read by argument object resolve and getter/setter hooks.
330 * NB: an element, once deleted, stays deleted. Thus:
332 * function f(x) { delete arguments[0]; arguments[0] = 42; return x }
333 * assertEq(f(1), 1);
335 * This works because, once a property is deleted from an arguments object,
336 * it gets regular properties with regular getters/setters that don't alias
337 * ArgumentsData::args.
339 bool isElementDeleted(uint32_t i) const {
340 MOZ_ASSERT(i < data()->numArgs);
341 if (i >= initialLength()) {
342 return false;
344 bool result = maybeRareData() &&
345 maybeRareData()->isElementDeleted(initialLength(), i);
346 MOZ_ASSERT_IF(result, hasOverriddenElement());
347 return result;
350 protected:
351 bool markElementDeleted(JSContext* cx, uint32_t i);
353 public:
355 * Return true iff the index is a valid element index for this arguments
356 * object.
358 * Returning true here doesn't imply that the element value can be read
359 * through |ArgumentsObject::element()|. For example unmapped arguments
360 * objects can have an element index property redefined without having marked
361 * the element as deleted. Instead use |maybeGetElement()| or manually check
362 * for |hasOverriddenElement()|.
364 bool isElement(uint32_t i) const {
365 return i < initialLength() && !isElementDeleted(i);
369 * An ArgumentsObject serves two roles:
370 * - a real object, accessed through regular object operations, e.g..,
371 * GetElement corresponding to 'arguments[i]';
372 * - a VM-internal data structure, storing the value of arguments (formal
373 * and actual) that are accessed directly by the VM when a reading the
374 * value of a formal parameter.
375 * There are two ways to access the ArgumentsData::args corresponding to
376 * these two use cases:
377 * - object access should use elements(i) which will take care of
378 * forwarding when the value is the magic forwarding value;
379 * - VM argument access should use arg(i) which will assert that the
380 * value is not the magic forwarding value (since, if such forwarding was
381 * needed, the frontend should have emitted JSOp::GetAliasedVar).
383 const Value& element(uint32_t i) const;
385 inline void setElement(uint32_t i, const Value& v);
387 const Value& arg(unsigned i) const {
388 MOZ_ASSERT(i < data()->numArgs);
389 const Value& v = data()->args[i];
390 MOZ_ASSERT(!v.isMagic());
391 return v;
394 void setArg(unsigned i, const Value& v) {
395 MOZ_ASSERT(i < data()->numArgs);
396 GCPtr<Value>& lhs = data()->args[i];
397 MOZ_ASSERT(!lhs.isMagic());
398 lhs = v;
402 * Test if an argument is forwarded, i.e. its actual value is stored in the
403 * CallObject and can't be directly read from |ArgumentsData::args|.
405 bool argIsForwarded(unsigned i) const {
406 MOZ_ASSERT(i < data()->numArgs);
407 const Value& v = data()->args[i];
408 MOZ_ASSERT_IF(IsMagicScopeSlotValue(v), anyArgIsForwarded());
409 return IsMagicScopeSlotValue(v);
412 bool anyArgIsForwarded() const {
413 const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT);
414 return v.toInt32() & FORWARDED_ARGUMENTS_BIT;
417 void markArgumentForwarded() {
418 uint32_t v =
419 getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | FORWARDED_ARGUMENTS_BIT;
420 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
424 * Attempt to speedily and efficiently access the i-th element of this
425 * arguments object. Return true if the element was speedily returned.
426 * Return false if the element must be looked up more slowly using
427 * getProperty or some similar method. The second overload copies the
428 * elements [start, start + count) into the locations starting at 'vp'.
430 * NB: Returning false does not indicate error!
432 bool maybeGetElement(uint32_t i, MutableHandleValue vp) {
433 if (i >= initialLength() || hasOverriddenElement()) {
434 return false;
436 vp.set(element(i));
437 return true;
440 inline bool maybeGetElements(uint32_t start, uint32_t count, js::Value* vp);
443 * Measures things hanging off this ArgumentsObject that are counted by the
444 * |miscSize| argument in JSObject::sizeOfExcludingThis().
446 size_t sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const {
447 if (!data()) { // Template arguments objects have no data.
448 return 0;
450 return mallocSizeOf(data()) + mallocSizeOf(maybeRareData());
452 size_t sizeOfData() const {
453 return ArgumentsData::bytesRequired(data()->numArgs) +
454 (maybeRareData() ? RareArgumentsData::bytesRequired(initialLength())
455 : 0);
458 static void finalize(JS::GCContext* gcx, JSObject* obj);
459 static void trace(JSTracer* trc, JSObject* obj);
460 static size_t objectMoved(JSObject* dst, JSObject* src);
462 /* For jit use: */
463 static size_t getDataSlotOffset() { return getFixedSlotOffset(DATA_SLOT); }
464 static size_t getInitialLengthSlotOffset() {
465 return getFixedSlotOffset(INITIAL_LENGTH_SLOT);
468 static Value MagicEnvSlotValue(uint32_t slot) {
469 // When forwarding slots to a backing CallObject, the slot numbers are
470 // stored as uint32 magic values. This raises an ambiguity if we have
471 // also copied JS_OPTIMIZED_OUT magic from a JIT frame or
472 // JS_UNINITIALIZED_LEXICAL magic on the CallObject. To distinguish
473 // normal magic values (those with a JSWhyMagic) and uint32 magic
474 // values, we add the maximum JSWhyMagic value to the slot
475 // number. This is safe as ARGS_LENGTH_MAX is well below UINT32_MAX.
476 static_assert(UINT32_MAX - JS_WHY_MAGIC_COUNT > ARGS_LENGTH_MAX);
477 return JS::MagicValueUint32(slot + JS_WHY_MAGIC_COUNT);
479 static uint32_t SlotFromMagicScopeSlotValue(const Value& v) {
480 static_assert(UINT32_MAX - JS_WHY_MAGIC_COUNT > ARGS_LENGTH_MAX);
481 return v.magicUint32() - JS_WHY_MAGIC_COUNT;
483 static bool IsMagicScopeSlotValue(const Value& v) {
484 return v.isMagic() && v.magicUint32() > JS_WHY_MAGIC_COUNT;
487 static void MaybeForwardToCallObject(AbstractFramePtr frame,
488 ArgumentsObject* obj,
489 ArgumentsData* data);
490 static void MaybeForwardToCallObject(JSFunction* callee, JSObject* callObj,
491 ArgumentsObject* obj,
492 ArgumentsData* data);
495 class MappedArgumentsObject : public ArgumentsObject {
496 static const JSClassOps classOps_;
497 static const ClassExtension classExt_;
498 static const ObjectOps objectOps_;
500 public:
501 static const JSClass class_;
503 JSFunction& callee() const {
504 return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
507 bool hasOverriddenCallee() const {
508 const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT);
509 return v.toInt32() & CALLEE_OVERRIDDEN_BIT;
512 void markCalleeOverridden() {
513 uint32_t v =
514 getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | CALLEE_OVERRIDDEN_BIT;
515 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
518 static size_t getCalleeSlotOffset() {
519 return getFixedSlotOffset(CALLEE_SLOT);
522 // Create the default "callee" property and set CALLEE_OVERRIDDEN_BIT.
523 static bool reifyCallee(JSContext* cx, Handle<MappedArgumentsObject*> obj);
525 private:
526 static bool obj_enumerate(JSContext* cx, HandleObject obj);
527 static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id,
528 bool* resolvedp);
529 static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
530 Handle<JS::PropertyDescriptor> desc,
531 ObjectOpResult& result);
534 class UnmappedArgumentsObject : public ArgumentsObject {
535 static const JSClassOps classOps_;
536 static const ClassExtension classExt_;
538 public:
539 static const JSClass class_;
541 private:
542 static bool obj_enumerate(JSContext* cx, HandleObject obj);
543 static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id,
544 bool* resolvedp);
547 extern bool MappedArgGetter(JSContext* cx, HandleObject obj, HandleId id,
548 MutableHandleValue vp);
550 extern bool MappedArgSetter(JSContext* cx, HandleObject obj, HandleId id,
551 HandleValue v, ObjectOpResult& result);
553 extern bool UnmappedArgGetter(JSContext* cx, HandleObject obj, HandleId id,
554 MutableHandleValue vp);
556 extern bool UnmappedArgSetter(JSContext* cx, HandleObject obj, HandleId id,
557 HandleValue v, ObjectOpResult& result);
559 } // namespace js
561 template <>
562 inline bool JSObject::is<js::ArgumentsObject>() const {
563 return is<js::MappedArgumentsObject>() || is<js::UnmappedArgumentsObject>();
566 #endif /* vm_ArgumentsObject_h */