1 | /* |
2 | * Copyright (C) 2008-2019 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #pragma once |
27 | |
28 | #include "ClassInfo.h" |
29 | #include "ConcurrentJSLock.h" |
30 | #include "IndexingType.h" |
31 | #include "JSCJSValue.h" |
32 | #include "JSCast.h" |
33 | #include "JSType.h" |
34 | #include "PropertyName.h" |
35 | #include "PropertyNameArray.h" |
36 | #include "PropertyOffset.h" |
37 | #include "PutPropertySlot.h" |
38 | #include "StructureIDBlob.h" |
39 | #include "StructureRareData.h" |
40 | #include "StructureTransitionTable.h" |
41 | #include "JSTypeInfo.h" |
42 | #include "Watchpoint.h" |
43 | #include "WriteBarrierInlines.h" |
44 | #include <wtf/PrintStream.h> |
45 | |
46 | namespace WTF { |
47 | |
48 | class UniquedStringImpl; |
49 | |
50 | } // namespace WTF |
51 | |
52 | namespace JSC { |
53 | |
54 | class DeferGC; |
55 | class ; |
56 | class PropertyNameArray; |
57 | class PropertyNameArrayData; |
58 | class PropertyTable; |
59 | class StructureChain; |
60 | class StructureShape; |
61 | class SlotVisitor; |
62 | class JSString; |
63 | struct DumpContext; |
64 | |
65 | // The out-of-line property storage capacity to use when first allocating out-of-line |
66 | // storage. Note that all objects start out without having any out-of-line storage; |
67 | // this comes into play only on the first property store that exhausts inline storage. |
68 | static const unsigned initialOutOfLineCapacity = 4; |
69 | |
70 | // The factor by which to grow out-of-line storage when it is exhausted, after the |
71 | // initial allocation. |
72 | static const unsigned outOfLineGrowthFactor = 2; |
73 | |
74 | struct PropertyMapEntry { |
75 | UniquedStringImpl* key; |
76 | PropertyOffset offset; |
77 | uint8_t attributes; |
78 | |
79 | PropertyMapEntry() |
80 | : key(nullptr) |
81 | , offset(invalidOffset) |
82 | , attributes(0) |
83 | { |
84 | } |
85 | |
86 | PropertyMapEntry(UniquedStringImpl* key, PropertyOffset offset, unsigned attributes) |
87 | : key(key) |
88 | , offset(offset) |
89 | , attributes(attributes) |
90 | { |
91 | ASSERT(this->attributes == attributes); |
92 | } |
93 | }; |
94 | |
95 | class StructureFireDetail : public FireDetail { |
96 | public: |
97 | StructureFireDetail(const Structure* structure) |
98 | : m_structure(structure) |
99 | { |
100 | } |
101 | |
102 | void dump(PrintStream& out) const override; |
103 | |
104 | private: |
105 | const Structure* m_structure; |
106 | }; |
107 | |
108 | class DeferredStructureTransitionWatchpointFire : public DeferredWatchpointFire { |
109 | WTF_MAKE_NONCOPYABLE(DeferredStructureTransitionWatchpointFire); |
110 | public: |
111 | JS_EXPORT_PRIVATE DeferredStructureTransitionWatchpointFire(VM&, Structure*); |
112 | JS_EXPORT_PRIVATE ~DeferredStructureTransitionWatchpointFire(); |
113 | |
114 | void dump(PrintStream& out) const override; |
115 | |
116 | const Structure* structure() const { return m_structure; } |
117 | |
118 | private: |
119 | const Structure* m_structure; |
120 | }; |
121 | |
122 | class Structure final : public JSCell { |
123 | public: |
124 | friend class StructureTransitionTable; |
125 | |
126 | typedef JSCell Base; |
127 | static const unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal; |
128 | |
129 | enum PolyProtoTag { PolyProto }; |
130 | static Structure* create(VM&, JSGlobalObject*, JSValue prototype, const TypeInfo&, const ClassInfo*, IndexingType = NonArray, unsigned inlineCapacity = 0); |
131 | static Structure* create(PolyProtoTag, VM&, JSGlobalObject*, JSObject* prototype, const TypeInfo&, const ClassInfo*, IndexingType = NonArray, unsigned inlineCapacity = 0); |
132 | |
133 | ~Structure(); |
134 | |
135 | template<typename CellType, SubspaceAccess> |
136 | static IsoSubspace* subspaceFor(VM& vm) |
137 | { |
138 | return &vm.structureSpace; |
139 | } |
140 | |
141 | JS_EXPORT_PRIVATE static bool isValidPrototype(JSValue); |
142 | |
143 | protected: |
144 | void finishCreation(VM& vm) |
145 | { |
146 | Base::finishCreation(vm); |
147 | ASSERT(m_prototype.get().isEmpty() || isValidPrototype(m_prototype.get())); |
148 | } |
149 | |
150 | void finishCreation(VM& vm, const Structure* previous) |
151 | { |
152 | this->finishCreation(vm); |
153 | if (previous->hasRareData()) { |
154 | const StructureRareData* previousRareData = previous->rareData(); |
155 | if (previousRareData->hasSharedPolyProtoWatchpoint()) { |
156 | ensureRareData(vm); |
157 | rareData()->setSharedPolyProtoWatchpoint(previousRareData->copySharedPolyProtoWatchpoint()); |
158 | } |
159 | } |
160 | } |
161 | |
162 | void finishCreation(VM& vm, CreatingEarlyCellTag) |
163 | { |
164 | Base::finishCreation(vm, this, CreatingEarlyCell); |
165 | ASSERT(m_prototype); |
166 | ASSERT(m_prototype.isNull()); |
167 | ASSERT(!vm.structureStructure); |
168 | } |
169 | |
170 | public: |
171 | StructureID id() const { return m_blob.structureID(); } |
172 | int32_t objectInitializationBlob() const { return m_blob.blobExcludingStructureID(); } |
173 | int64_t idBlob() const { return m_blob.blob(); } |
174 | |
175 | bool isProxy() const |
176 | { |
177 | JSType type = m_blob.type(); |
178 | return type == ImpureProxyType || type == PureForwardingProxyType || type == ProxyObjectType; |
179 | } |
180 | |
181 | static void dumpStatistics(); |
182 | |
183 | JS_EXPORT_PRIVATE static Structure* addPropertyTransition(VM&, Structure*, PropertyName, unsigned attributes, PropertyOffset&); |
184 | JS_EXPORT_PRIVATE static Structure* addNewPropertyTransition(VM&, Structure*, PropertyName, unsigned attributes, PropertyOffset&, PutPropertySlot::Context = PutPropertySlot::UnknownContext, DeferredStructureTransitionWatchpointFire* = nullptr); |
185 | static Structure* addPropertyTransitionToExistingStructureConcurrently(Structure*, UniquedStringImpl* uid, unsigned attributes, PropertyOffset&); |
186 | JS_EXPORT_PRIVATE static Structure* addPropertyTransitionToExistingStructure(Structure*, PropertyName, unsigned attributes, PropertyOffset&); |
187 | static Structure* removePropertyTransition(VM&, Structure*, PropertyName, PropertyOffset&); |
188 | static Structure* changePrototypeTransition(VM&, Structure*, JSValue prototype, DeferredStructureTransitionWatchpointFire&); |
189 | JS_EXPORT_PRIVATE static Structure* attributeChangeTransition(VM&, Structure*, PropertyName, unsigned attributes); |
190 | JS_EXPORT_PRIVATE static Structure* toCacheableDictionaryTransition(VM&, Structure*, DeferredStructureTransitionWatchpointFire* = nullptr); |
191 | static Structure* toUncacheableDictionaryTransition(VM&, Structure*); |
192 | JS_EXPORT_PRIVATE static Structure* sealTransition(VM&, Structure*); |
193 | JS_EXPORT_PRIVATE static Structure* freezeTransition(VM&, Structure*); |
194 | static Structure* preventExtensionsTransition(VM&, Structure*); |
195 | static Structure* nonPropertyTransition(VM&, Structure*, NonPropertyTransition); |
196 | JS_EXPORT_PRIVATE static Structure* nonPropertyTransitionSlow(VM&, Structure*, NonPropertyTransition); |
197 | |
198 | JS_EXPORT_PRIVATE bool isSealed(VM&); |
199 | JS_EXPORT_PRIVATE bool isFrozen(VM&); |
200 | bool isStructureExtensible() const { return !didPreventExtensions(); } |
201 | |
202 | JS_EXPORT_PRIVATE Structure* flattenDictionaryStructure(VM&, JSObject*); |
203 | |
204 | static const bool needsDestruction = true; |
205 | static void destroy(JSCell*); |
206 | |
207 | // Versions that take a func will call it after making the change but while still holding |
208 | // the lock. The callback is not called if there is no change being made, like if you call |
209 | // removePropertyWithoutTransition() and the property is not found. |
210 | template<typename Func> |
211 | PropertyOffset addPropertyWithoutTransition(VM&, PropertyName, unsigned attributes, const Func&); |
212 | template<typename Func> |
213 | PropertyOffset removePropertyWithoutTransition(VM&, PropertyName, const Func&); |
214 | void setPrototypeWithoutTransition(VM&, JSValue prototype); |
215 | |
216 | bool isDictionary() const { return dictionaryKind() != NoneDictionaryKind; } |
217 | bool isUncacheableDictionary() const { return dictionaryKind() == UncachedDictionaryKind; } |
218 | |
219 | bool prototypeQueriesAreCacheable() |
220 | { |
221 | return !typeInfo().prohibitsPropertyCaching(); |
222 | } |
223 | |
224 | bool propertyAccessesAreCacheable() |
225 | { |
226 | return dictionaryKind() != UncachedDictionaryKind |
227 | && prototypeQueriesAreCacheable() |
228 | && !(typeInfo().getOwnPropertySlotIsImpure() && !typeInfo().newImpurePropertyFiresWatchpoints()); |
229 | } |
230 | |
231 | bool propertyAccessesAreCacheableForAbsence() |
232 | { |
233 | return !typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence(); |
234 | } |
235 | |
236 | bool needImpurePropertyWatchpoint() |
237 | { |
238 | return propertyAccessesAreCacheable() |
239 | && typeInfo().getOwnPropertySlotIsImpure() |
240 | && typeInfo().newImpurePropertyFiresWatchpoints(); |
241 | } |
242 | |
243 | bool isImmutablePrototypeExoticObject() |
244 | { |
245 | return typeInfo().isImmutablePrototypeExoticObject(); |
246 | } |
247 | |
248 | // We use SlowPath in GetByIdStatus for structures that may get new impure properties later to prevent |
249 | // DFG from inlining property accesses since structures don't transition when a new impure property appears. |
250 | bool takesSlowPathInDFGForImpureProperty() |
251 | { |
252 | return typeInfo().getOwnPropertySlotIsImpure(); |
253 | } |
254 | |
255 | // Type accessors. |
256 | TypeInfo typeInfo() const { return m_blob.typeInfo(m_outOfLineTypeFlags); } |
257 | bool isObject() const { return typeInfo().isObject(); } |
258 | |
259 | IndexingType indexingType() const { return m_blob.indexingModeIncludingHistory() & AllWritableArrayTypes; } |
260 | IndexingType indexingMode() const { return m_blob.indexingModeIncludingHistory() & AllArrayTypes; } |
261 | IndexingType indexingModeIncludingHistory() const { return m_blob.indexingModeIncludingHistory(); } |
262 | |
263 | inline bool mayInterceptIndexedAccesses() const; |
264 | |
265 | bool holesMustForwardToPrototype(VM&, JSObject*) const; |
266 | |
267 | JSGlobalObject* globalObject() const { return m_globalObject.get(); } |
268 | |
269 | // NOTE: This method should only be called during the creation of structures, since the global |
270 | // object of a structure is presumed to be immutable in a bunch of places. |
271 | void setGlobalObject(VM&, JSGlobalObject*); |
272 | |
273 | ALWAYS_INLINE bool hasMonoProto() const |
274 | { |
275 | return !m_prototype.get().isEmpty(); |
276 | } |
277 | ALWAYS_INLINE bool hasPolyProto() const |
278 | { |
279 | return !hasMonoProto(); |
280 | } |
281 | ALWAYS_INLINE JSValue storedPrototype() const |
282 | { |
283 | ASSERT(hasMonoProto()); |
284 | return m_prototype.get(); |
285 | } |
286 | JSValue storedPrototype(const JSObject*) const; |
287 | JSObject* storedPrototypeObject(const JSObject*) const; |
288 | Structure* storedPrototypeStructure(const JSObject*) const; |
289 | |
290 | JSObject* storedPrototypeObject() const; |
291 | Structure* storedPrototypeStructure() const; |
292 | JSValue prototypeForLookup(JSGlobalObject*) const; |
293 | JSValue prototypeForLookup(JSGlobalObject*, JSCell* base) const; |
294 | StructureChain* prototypeChain(VM&, JSGlobalObject*, JSObject* base) const; |
295 | StructureChain* prototypeChain(ExecState*, JSObject* base) const; |
296 | static void visitChildren(JSCell*, SlotVisitor&); |
297 | |
298 | // A Structure is cheap to mark during GC if doing so would only add a small and bounded amount |
299 | // to our heap footprint. For example, if the structure refers to a global object that is not |
300 | // yet marked, then as far as we know, the decision to mark this Structure would lead to a large |
301 | // increase in footprint because no other object refers to that global object. This method |
302 | // returns true if all user-controlled (and hence unbounded in size) objects referenced from the |
303 | // Structure are already marked. |
304 | bool isCheapDuringGC(VM&); |
305 | |
306 | // Returns true if this structure is now marked. |
307 | bool markIfCheap(SlotVisitor&); |
308 | |
309 | bool hasRareData() const |
310 | { |
311 | return isRareData(m_previousOrRareData.get()); |
312 | } |
313 | |
314 | StructureRareData* rareData() |
315 | { |
316 | ASSERT(hasRareData()); |
317 | return static_cast<StructureRareData*>(m_previousOrRareData.get()); |
318 | } |
319 | |
320 | const StructureRareData* rareData() const |
321 | { |
322 | ASSERT(hasRareData()); |
323 | return static_cast<const StructureRareData*>(m_previousOrRareData.get()); |
324 | } |
325 | |
326 | const StructureRareData* rareDataConcurrently() const |
327 | { |
328 | JSCell* cell = m_previousOrRareData.get(); |
329 | if (isRareData(cell)) |
330 | return static_cast<StructureRareData*>(cell); |
331 | return nullptr; |
332 | } |
333 | |
334 | StructureRareData* ensureRareData(VM& vm) |
335 | { |
336 | if (!hasRareData()) |
337 | allocateRareData(vm); |
338 | return rareData(); |
339 | } |
340 | |
341 | Structure* previousID() const |
342 | { |
343 | ASSERT(structure()->classInfo() == info()); |
344 | // This is so written because it's used concurrently. We only load from m_previousOrRareData |
345 | // once, and this load is guaranteed atomic. |
346 | JSCell* cell = m_previousOrRareData.get(); |
347 | if (isRareData(cell)) |
348 | return static_cast<StructureRareData*>(cell)->previousID(); |
349 | return static_cast<Structure*>(cell); |
350 | } |
351 | bool transitivelyTransitionedFrom(Structure* structureToFind); |
352 | |
353 | PropertyOffset lastOffset() const { return m_offset; } |
354 | |
355 | void setLastOffset(PropertyOffset offset) { m_offset = offset; } |
356 | |
357 | static unsigned outOfLineCapacity(PropertyOffset lastOffset) |
358 | { |
359 | unsigned outOfLineSize = Structure::outOfLineSize(lastOffset); |
360 | |
361 | // This algorithm completely determines the out-of-line property storage growth algorithm. |
362 | // The JSObject code will only trigger a resize if the value returned by this algorithm |
363 | // changed between the new and old structure. So, it's important to keep this simple because |
364 | // it's on a fast path. |
365 | |
366 | if (!outOfLineSize) |
367 | return 0; |
368 | |
369 | if (outOfLineSize <= initialOutOfLineCapacity) |
370 | return initialOutOfLineCapacity; |
371 | |
372 | ASSERT(outOfLineSize > initialOutOfLineCapacity); |
373 | COMPILE_ASSERT(outOfLineGrowthFactor == 2, outOfLineGrowthFactor_is_two); |
374 | return WTF::roundUpToPowerOfTwo(outOfLineSize); |
375 | } |
376 | |
377 | static unsigned outOfLineSize(PropertyOffset lastOffset) |
378 | { |
379 | return numberOfOutOfLineSlotsForLastOffset(lastOffset); |
380 | } |
381 | |
382 | unsigned outOfLineCapacity() const |
383 | { |
384 | return outOfLineCapacity(m_offset); |
385 | } |
386 | unsigned outOfLineSize() const |
387 | { |
388 | return outOfLineSize(m_offset); |
389 | } |
390 | bool hasInlineStorage() const |
391 | { |
392 | return !!m_inlineCapacity; |
393 | } |
394 | unsigned inlineCapacity() const |
395 | { |
396 | return m_inlineCapacity; |
397 | } |
398 | unsigned inlineSize() const |
399 | { |
400 | return std::min<unsigned>(m_offset + 1, m_inlineCapacity); |
401 | } |
402 | unsigned totalStorageSize() const |
403 | { |
404 | return numberOfSlotsForLastOffset(m_offset, m_inlineCapacity); |
405 | } |
406 | unsigned totalStorageCapacity() const |
407 | { |
408 | ASSERT(structure()->classInfo() == info()); |
409 | return outOfLineCapacity() + inlineCapacity(); |
410 | } |
411 | |
412 | bool isValidOffset(PropertyOffset offset) const |
413 | { |
414 | return JSC::isValidOffset(offset) |
415 | && offset <= m_offset |
416 | && (offset < m_inlineCapacity || offset >= firstOutOfLineOffset); |
417 | } |
418 | |
419 | bool () const |
420 | { |
421 | return isTypedView(m_classInfo->typedArrayStorageType); |
422 | } |
423 | |
424 | bool () const |
425 | { |
426 | return hasIndexedProperties(indexingType()) |
427 | || hijacksIndexingHeader(); |
428 | } |
429 | |
430 | bool (const JSCell*) const; |
431 | |
432 | bool masqueradesAsUndefined(JSGlobalObject* lexicalGlobalObject); |
433 | |
434 | PropertyOffset get(VM&, PropertyName); |
435 | PropertyOffset get(VM&, PropertyName, unsigned& attributes); |
436 | |
437 | // This is a somewhat internalish method. It will call your functor while possibly holding the |
438 | // Structure's lock. There is no guarantee whether the lock is held or not in any particular |
439 | // call. So, you have to assume the worst. Also, the functor returns true if it wishes for you |
440 | // to continue or false if it's done. |
441 | template<typename Functor> |
442 | void forEachPropertyConcurrently(const Functor&); |
443 | |
444 | template<typename Functor> |
445 | void forEachProperty(VM&, const Functor&); |
446 | |
447 | PropertyOffset getConcurrently(UniquedStringImpl* uid); |
448 | PropertyOffset getConcurrently(UniquedStringImpl* uid, unsigned& attributes); |
449 | |
450 | Vector<PropertyMapEntry> getPropertiesConcurrently(); |
451 | |
452 | void setHasGetterSetterPropertiesWithProtoCheck(bool is__proto__) |
453 | { |
454 | setHasGetterSetterProperties(true); |
455 | if (!is__proto__) |
456 | setHasReadOnlyOrGetterSetterPropertiesExcludingProto(true); |
457 | } |
458 | |
459 | void setContainsReadOnlyProperties() { setHasReadOnlyOrGetterSetterPropertiesExcludingProto(true); } |
460 | |
461 | void setHasCustomGetterSetterPropertiesWithProtoCheck(bool is__proto__) |
462 | { |
463 | setHasCustomGetterSetterProperties(true); |
464 | if (!is__proto__) |
465 | setHasReadOnlyOrGetterSetterPropertiesExcludingProto(true); |
466 | } |
467 | |
468 | bool isEmpty() const |
469 | { |
470 | ASSERT(checkOffsetConsistency()); |
471 | return !JSC::isValidOffset(m_offset); |
472 | } |
473 | |
474 | void setCachedPropertyNameEnumerator(VM&, JSPropertyNameEnumerator*); |
475 | JSPropertyNameEnumerator* cachedPropertyNameEnumerator() const; |
476 | bool canCachePropertyNameEnumerator() const; |
477 | bool canAccessPropertiesQuicklyForEnumeration() const; |
478 | |
479 | void setCachedOwnKeys(VM&, JSImmutableButterfly*); |
480 | JSImmutableButterfly* cachedOwnKeys() const; |
481 | JSImmutableButterfly* cachedOwnKeysIgnoringSentinel() const; |
482 | bool canCacheOwnKeys() const; |
483 | |
484 | void getPropertyNamesFromStructure(VM&, PropertyNameArray&, EnumerationMode); |
485 | |
486 | JSString* objectToStringValue() |
487 | { |
488 | if (!hasRareData()) |
489 | return 0; |
490 | return rareData()->objectToStringValue(); |
491 | } |
492 | |
493 | void setObjectToStringValue(ExecState*, VM&, JSString* value, PropertySlot toStringTagSymbolSlot); |
494 | |
495 | const ClassInfo* classInfo() const { return m_classInfo; } |
496 | |
497 | static ptrdiff_t structureIDOffset() |
498 | { |
499 | return OBJECT_OFFSETOF(Structure, m_blob) + StructureIDBlob::structureIDOffset(); |
500 | } |
501 | |
502 | static ptrdiff_t prototypeOffset() |
503 | { |
504 | return OBJECT_OFFSETOF(Structure, m_prototype); |
505 | } |
506 | |
507 | static ptrdiff_t globalObjectOffset() |
508 | { |
509 | return OBJECT_OFFSETOF(Structure, m_globalObject); |
510 | } |
511 | |
512 | static ptrdiff_t classInfoOffset() |
513 | { |
514 | return OBJECT_OFFSETOF(Structure, m_classInfo); |
515 | } |
516 | |
517 | static ptrdiff_t indexingModeIncludingHistoryOffset() |
518 | { |
519 | return OBJECT_OFFSETOF(Structure, m_blob) + StructureIDBlob::indexingModeIncludingHistoryOffset(); |
520 | } |
521 | |
522 | static ptrdiff_t propertyTableUnsafeOffset() |
523 | { |
524 | return OBJECT_OFFSETOF(Structure, m_propertyTableUnsafe); |
525 | } |
526 | |
527 | static ptrdiff_t inlineCapacityOffset() |
528 | { |
529 | return OBJECT_OFFSETOF(Structure, m_inlineCapacity); |
530 | } |
531 | |
532 | static ptrdiff_t previousOrRareDataOffset() |
533 | { |
534 | return OBJECT_OFFSETOF(Structure, m_previousOrRareData); |
535 | } |
536 | |
537 | static Structure* createStructure(VM&); |
538 | |
539 | bool transitionWatchpointSetHasBeenInvalidated() const |
540 | { |
541 | return m_transitionWatchpointSet.hasBeenInvalidated(); |
542 | } |
543 | |
544 | bool transitionWatchpointSetIsStillValid() const |
545 | { |
546 | return m_transitionWatchpointSet.isStillValid(); |
547 | } |
548 | |
549 | bool dfgShouldWatchIfPossible() const |
550 | { |
551 | // FIXME: We would like to not watch things that are unprofitable to watch, like |
552 | // dictionaries. Unfortunately, we can't do such things: a dictionary could get flattened, |
553 | // in which case it will start to appear watchable and so the DFG will think that it is |
554 | // watching it. We should come up with a comprehensive story for not watching things that |
555 | // aren't profitable to watch. |
556 | // https://bugs.webkit.org/show_bug.cgi?id=133625 |
557 | |
558 | // - We don't watch Structures that either decided not to be watched, or whose predecessors |
559 | // decided not to be watched. This happens when a transition is fired while being watched. |
560 | if (transitionWatchpointIsLikelyToBeFired()) |
561 | return false; |
562 | |
563 | // - Don't watch Structures that had been dictionaries. |
564 | if (hasBeenDictionary()) |
565 | return false; |
566 | |
567 | return true; |
568 | } |
569 | |
570 | bool dfgShouldWatch() const |
571 | { |
572 | return dfgShouldWatchIfPossible() && transitionWatchpointSetIsStillValid(); |
573 | } |
574 | |
575 | void addTransitionWatchpoint(Watchpoint* watchpoint) const |
576 | { |
577 | ASSERT(transitionWatchpointSetIsStillValid()); |
578 | m_transitionWatchpointSet.add(watchpoint); |
579 | } |
580 | |
581 | void didTransitionFromThisStructure(DeferredStructureTransitionWatchpointFire* = nullptr) const; |
582 | |
583 | InlineWatchpointSet& transitionWatchpointSet() const |
584 | { |
585 | return m_transitionWatchpointSet; |
586 | } |
587 | |
588 | WatchpointSet* ensurePropertyReplacementWatchpointSet(VM&, PropertyOffset); |
589 | void startWatchingPropertyForReplacements(VM& vm, PropertyOffset offset) |
590 | { |
591 | ensurePropertyReplacementWatchpointSet(vm, offset); |
592 | } |
593 | void startWatchingPropertyForReplacements(VM&, PropertyName); |
594 | WatchpointSet* propertyReplacementWatchpointSet(PropertyOffset); |
595 | void didReplaceProperty(PropertyOffset); |
596 | void didCachePropertyReplacement(VM&, PropertyOffset); |
597 | |
598 | void startWatchingInternalPropertiesIfNecessary(VM& vm) |
599 | { |
600 | if (LIKELY(didWatchInternalProperties())) |
601 | return; |
602 | startWatchingInternalProperties(vm); |
603 | } |
604 | |
605 | Ref<StructureShape> toStructureShape(JSValue, bool& sawPolyProtoStructure); |
606 | |
607 | void dump(PrintStream&) const; |
608 | void dumpInContext(PrintStream&, DumpContext*) const; |
609 | void dumpBrief(PrintStream&, const CString&) const; |
610 | |
611 | static void (PrintStream&); |
612 | |
613 | ConcurrentJSLock& lock() { return m_lock; } |
614 | |
615 | unsigned propertyHash() const { return m_propertyHash; } |
616 | |
617 | static bool shouldConvertToPolyProto(const Structure* a, const Structure* b); |
618 | |
619 | DECLARE_EXPORT_INFO; |
620 | |
621 | private: |
622 | typedef enum { |
623 | NoneDictionaryKind = 0, |
624 | CachedDictionaryKind = 1, |
625 | UncachedDictionaryKind = 2 |
626 | } DictionaryKind; |
627 | |
628 | public: |
629 | #define DEFINE_BITFIELD(type, lowerName, upperName, width, offset) \ |
630 | static const uint32_t s_##lowerName##Shift = offset;\ |
631 | static const uint32_t s_##lowerName##Mask = ((1 << (width - 1)) | ((1 << (width - 1)) - 1));\ |
632 | type lowerName() const { return static_cast<type>((m_bitField >> offset) & s_##lowerName##Mask); }\ |
633 | void set##upperName(type newValue) \ |
634 | {\ |
635 | m_bitField &= ~(s_##lowerName##Mask << offset);\ |
636 | m_bitField |= (newValue & s_##lowerName##Mask) << offset;\ |
637 | } |
638 | |
639 | DEFINE_BITFIELD(DictionaryKind, dictionaryKind, DictionaryKind, 2, 0); |
640 | DEFINE_BITFIELD(bool, isPinnedPropertyTable, IsPinnedPropertyTable, 1, 2); |
641 | DEFINE_BITFIELD(bool, hasGetterSetterProperties, HasGetterSetterProperties, 1, 3); |
642 | DEFINE_BITFIELD(bool, hasReadOnlyOrGetterSetterPropertiesExcludingProto, HasReadOnlyOrGetterSetterPropertiesExcludingProto, 1, 4); |
643 | DEFINE_BITFIELD(bool, isQuickPropertyAccessAllowedForEnumeration, IsQuickPropertyAccessAllowedForEnumeration, 1, 5); |
644 | DEFINE_BITFIELD(unsigned, attributesInPrevious, AttributesInPrevious, 14, 6); |
645 | DEFINE_BITFIELD(bool, didPreventExtensions, DidPreventExtensions, 1, 20); |
646 | DEFINE_BITFIELD(bool, didTransition, DidTransition, 1, 21); |
647 | DEFINE_BITFIELD(bool, staticPropertiesReified, StaticPropertiesReified, 1, 22); |
648 | DEFINE_BITFIELD(bool, hasBeenFlattenedBefore, HasBeenFlattenedBefore, 1, 23); |
649 | DEFINE_BITFIELD(bool, hasCustomGetterSetterProperties, HasCustomGetterSetterProperties, 1, 24); |
650 | DEFINE_BITFIELD(bool, didWatchInternalProperties, DidWatchInternalProperties, 1, 25); |
651 | DEFINE_BITFIELD(bool, transitionWatchpointIsLikelyToBeFired, TransitionWatchpointIsLikelyToBeFired, 1, 26); |
652 | DEFINE_BITFIELD(bool, hasBeenDictionary, HasBeenDictionary, 1, 27); |
653 | DEFINE_BITFIELD(bool, isAddingPropertyForTransition, IsAddingPropertyForTransition, 1, 28); |
654 | DEFINE_BITFIELD(bool, hasUnderscoreProtoPropertyExcludingOriginalProto, HasUnderscoreProtoPropertyExcludingOriginalProto, 1, 29); |
655 | |
656 | private: |
657 | friend class LLIntOffsetsExtractor; |
658 | |
659 | JS_EXPORT_PRIVATE Structure(VM&, JSGlobalObject*, JSValue prototype, const TypeInfo&, const ClassInfo*, IndexingType, unsigned inlineCapacity); |
660 | Structure(VM&); |
661 | Structure(VM&, Structure*, DeferredStructureTransitionWatchpointFire*); |
662 | |
663 | static Structure* create(VM&, Structure*, DeferredStructureTransitionWatchpointFire* = nullptr); |
664 | |
665 | static Structure* addPropertyTransitionToExistingStructureImpl(Structure*, UniquedStringImpl* uid, unsigned attributes, PropertyOffset&); |
666 | |
667 | // This will return the structure that has a usable property table, that property table, |
668 | // and the list of structures that we visited before we got to it. If it returns a |
669 | // non-null structure, it will also lock the structure that it returns; it is your job |
670 | // to unlock it. |
671 | void findStructuresAndMapForMaterialization(Vector<Structure*, 8>& structures, Structure*&, PropertyTable*&); |
672 | |
673 | static Structure* toDictionaryTransition(VM&, Structure*, DictionaryKind, DeferredStructureTransitionWatchpointFire* = nullptr); |
674 | |
675 | enum class ShouldPin { No, Yes }; |
676 | template<ShouldPin, typename Func> |
677 | PropertyOffset add(VM&, PropertyName, unsigned attributes, const Func&); |
678 | PropertyOffset add(VM&, PropertyName, unsigned attributes); |
679 | template<typename Func> |
680 | PropertyOffset remove(PropertyName, const Func&); |
681 | PropertyOffset remove(PropertyName); |
682 | |
683 | void checkConsistency(); |
684 | |
685 | // This may grab the lock, or not. Do not call when holding the Structure's lock. |
686 | PropertyTable* ensurePropertyTableIfNotEmpty(VM& vm) |
687 | { |
688 | if (PropertyTable* result = m_propertyTableUnsafe.get()) |
689 | return result; |
690 | if (!previousID()) |
691 | return nullptr; |
692 | return materializePropertyTable(vm); |
693 | } |
694 | |
695 | // This may grab the lock, or not. Do not call when holding the Structure's lock. |
696 | PropertyTable* ensurePropertyTable(VM& vm) |
697 | { |
698 | if (PropertyTable* result = m_propertyTableUnsafe.get()) |
699 | return result; |
700 | return materializePropertyTable(vm); |
701 | } |
702 | |
703 | PropertyTable* propertyTableOrNull() const |
704 | { |
705 | return m_propertyTableUnsafe.get(); |
706 | } |
707 | |
708 | // This will grab the lock. Do not call when holding the Structure's lock. |
709 | JS_EXPORT_PRIVATE PropertyTable* materializePropertyTable(VM&, bool setPropertyTable = true); |
710 | |
711 | void setPropertyTable(VM& vm, PropertyTable* table); |
712 | |
713 | PropertyTable* takePropertyTableOrCloneIfPinned(VM&); |
714 | PropertyTable* copyPropertyTableForPinning(VM&); |
715 | |
716 | void setPreviousID(VM&, Structure*); |
717 | |
718 | void clearPreviousID() |
719 | { |
720 | if (hasRareData()) |
721 | rareData()->clearPreviousID(); |
722 | else |
723 | m_previousOrRareData.clear(); |
724 | } |
725 | |
726 | int transitionCount() const |
727 | { |
728 | // Since the number of transitions is always the same as m_offset, we keep the size of Structure down by not storing both. |
729 | return numberOfSlotsForLastOffset(m_offset, m_inlineCapacity); |
730 | } |
731 | |
732 | bool isValid(JSGlobalObject*, StructureChain* cachedPrototypeChain, JSObject* base) const; |
733 | |
734 | // You have to hold the structure lock to do these. |
735 | JS_EXPORT_PRIVATE void pin(const AbstractLocker&, VM&, PropertyTable*); |
736 | void pinForCaching(const AbstractLocker&, VM&, PropertyTable*); |
737 | |
738 | bool isRareData(JSCell* cell) const |
739 | { |
740 | return cell && cell->structureID() != structureID(); |
741 | } |
742 | |
743 | template<typename DetailsFunc> |
744 | bool checkOffsetConsistency(PropertyTable*, const DetailsFunc&) const; |
745 | bool checkOffsetConsistency() const; |
746 | |
747 | JS_EXPORT_PRIVATE void allocateRareData(VM&); |
748 | |
749 | void startWatchingInternalProperties(VM&); |
750 | |
751 | static const int s_maxTransitionLength = 64; |
752 | static const int s_maxTransitionLengthForNonEvalPutById = 512; |
753 | |
754 | // These need to be properly aligned at the beginning of the 'Structure' |
755 | // part of the object. |
756 | StructureIDBlob m_blob; |
757 | TypeInfo::OutOfLineTypeFlags m_outOfLineTypeFlags; |
758 | |
759 | uint8_t m_inlineCapacity; |
760 | |
761 | ConcurrentJSLock m_lock; |
762 | |
763 | uint32_t m_bitField; |
764 | |
765 | WriteBarrier<JSGlobalObject> m_globalObject; |
766 | WriteBarrier<Unknown> m_prototype; |
767 | mutable WriteBarrier<StructureChain> m_cachedPrototypeChain; |
768 | |
769 | WriteBarrier<JSCell> m_previousOrRareData; |
770 | |
771 | RefPtr<UniquedStringImpl> m_nameInPrevious; |
772 | |
773 | const ClassInfo* m_classInfo; |
774 | |
775 | StructureTransitionTable m_transitionTable; |
776 | |
777 | // Should be accessed through ensurePropertyTable(). During GC, it may be set to 0 by another thread. |
778 | // During a Heap Snapshot GC we avoid clearing the table so it is safe to use. |
779 | WriteBarrier<PropertyTable> m_propertyTableUnsafe; |
780 | |
781 | mutable InlineWatchpointSet m_transitionWatchpointSet; |
782 | |
783 | COMPILE_ASSERT(firstOutOfLineOffset < 256, firstOutOfLineOffset_fits); |
784 | |
785 | // m_offset does not account for anonymous slots |
786 | PropertyOffset m_offset; |
787 | |
788 | uint32_t m_propertyHash; |
789 | }; |
790 | |
791 | } // namespace JSC |
792 | |