1/*
2 * Copyright (C) 2017 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 "ObjectAllocationProfile.h"
29
30#include "JSFunctionInlines.h"
31
32namespace JSC {
33
34ALWAYS_INLINE void ObjectAllocationProfile::initializeProfile(VM& vm, JSGlobalObject* globalObject, JSCell* owner, JSObject* prototype, unsigned inferredInlineCapacity, JSFunction* constructor, FunctionRareData* functionRareData)
35{
36 ASSERT(!m_allocator);
37 ASSERT(!m_structure);
38 ASSERT(!m_prototype);
39 ASSERT(!m_inlineCapacity);
40
41 // FIXME: Teach create_this's fast path how to allocate poly
42 // proto objects: https://bugs.webkit.org/show_bug.cgi?id=177517
43
44 bool isPolyProto = false;
45 FunctionExecutable* executable = nullptr;
46 if (constructor) {
47 // FIXME: A JSFunction should watch the poly proto watchpoint if it is not invalidated.
48 // That way it can clear this object allocation profile to ensure it stops allocating
49 // mono proto |this| values when it knows that it should be allocating poly proto
50 // |this| values:
51 // https://bugs.webkit.org/show_bug.cgi?id=177792
52
53 executable = constructor->jsExecutable();
54
55 if (Structure* structure = executable->cachedPolyProtoStructure()) {
56 RELEASE_ASSERT(structure->typeInfo().type() == FinalObjectType);
57 m_allocator = Allocator();
58 m_structure.set(vm, owner, structure);
59 m_prototype.set(vm, owner, prototype);
60 m_inlineCapacity = structure->inlineCapacity();
61 return;
62 }
63
64 isPolyProto = false;
65 if (Options::forcePolyProto())
66 isPolyProto = true;
67 else
68 isPolyProto = executable->ensurePolyProtoWatchpoint().hasBeenInvalidated() && executable->singletonFunctionHasBeenInvalidated();
69 }
70
71 unsigned inlineCapacity = 0;
72 if (inferredInlineCapacity < JSFinalObject::defaultInlineCapacity()) {
73 // Try to shrink the object based on static analysis.
74 inferredInlineCapacity += possibleDefaultPropertyCount(vm, prototype);
75
76 if (!inferredInlineCapacity) {
77 // Empty objects are rare, so most likely the static analyzer just didn't
78 // see the real initializer function. This can happen with helper functions.
79 inferredInlineCapacity = JSFinalObject::defaultInlineCapacity();
80 } else if (inferredInlineCapacity > JSFinalObject::defaultInlineCapacity()) {
81 // Default properties are weak guesses, so don't allow them to turn a small
82 // object into a large object.
83 inferredInlineCapacity = JSFinalObject::defaultInlineCapacity();
84 }
85
86 inlineCapacity = inferredInlineCapacity;
87 ASSERT(inlineCapacity < JSFinalObject::maxInlineCapacity());
88 } else {
89 // Normal or large object.
90 inlineCapacity = inferredInlineCapacity;
91 if (inlineCapacity > JSFinalObject::maxInlineCapacity())
92 inlineCapacity = JSFinalObject::maxInlineCapacity();
93 }
94
95 if (isPolyProto) {
96 ++inlineCapacity;
97 inlineCapacity = std::min(inlineCapacity, JSFinalObject::maxInlineCapacity());
98 }
99
100 ASSERT(inlineCapacity > 0);
101 ASSERT(inlineCapacity <= JSFinalObject::maxInlineCapacity());
102
103 size_t allocationSize = JSFinalObject::allocationSize(inlineCapacity);
104 Allocator allocator = subspaceFor<JSFinalObject>(vm)->allocatorForNonVirtual(allocationSize, AllocatorForMode::EnsureAllocator);
105
106 // Take advantage of extra inline capacity available in the size class.
107 if (allocator) {
108 size_t slop = (allocator.cellSize() - allocationSize) / sizeof(WriteBarrier<Unknown>);
109 inlineCapacity += slop;
110 if (inlineCapacity > JSFinalObject::maxInlineCapacity())
111 inlineCapacity = JSFinalObject::maxInlineCapacity();
112 }
113
114 Structure* structure = vm.structureCache.emptyObjectStructureForPrototype(globalObject, prototype, inlineCapacity, isPolyProto, executable);
115
116 if (isPolyProto) {
117 ASSERT(structure->hasPolyProto());
118 m_allocator = Allocator();
119 executable->setCachedPolyProtoStructure(vm, structure);
120 } else {
121 if (executable) {
122 ASSERT(constructor);
123 ASSERT(functionRareData);
124 InlineWatchpointSet& polyProtoWatchpointSet = executable->ensurePolyProtoWatchpoint();
125 structure->ensureRareData(vm)->setSharedPolyProtoWatchpoint(executable->sharedPolyProtoWatchpoint());
126 if (polyProtoWatchpointSet.isStillValid() && !functionRareData->hasAllocationProfileClearingWatchpoint()) {
127 // If we happen to go poly proto in the future, we want to clear this particular
128 // object allocation profile so we can transition to allocating poly proto objects.
129 Watchpoint* watchpoint = functionRareData->createAllocationProfileClearingWatchpoint();
130 polyProtoWatchpointSet.add(watchpoint);
131 }
132 }
133
134 m_allocator = allocator;
135 }
136
137 // Ensure that if another thread sees the structure and prototype, it will see it properly created.
138 WTF::storeStoreFence();
139
140 m_structure.set(vm, owner, structure);
141 m_prototype.set(vm, owner, prototype);
142 m_inlineCapacity = inlineCapacity;
143}
144
145ALWAYS_INLINE unsigned ObjectAllocationProfile::possibleDefaultPropertyCount(VM& vm, JSObject* prototype)
146{
147 if (prototype == prototype->globalObject(vm)->objectPrototype())
148 return 0;
149
150 size_t count = 0;
151 PropertyNameArray propertyNameArray(&vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include);
152 prototype->structure(vm)->getPropertyNamesFromStructure(vm, propertyNameArray, EnumerationMode());
153 PropertyNameArrayData::PropertyNameVector& propertyNameVector = propertyNameArray.data()->propertyNameVector();
154 for (size_t i = 0; i < propertyNameVector.size(); ++i) {
155 JSValue value = prototype->getDirect(vm, propertyNameVector[i]);
156
157 // Functions are common, and are usually class-level objects that are not overridden.
158 if (jsDynamicCast<JSFunction*>(vm, value))
159 continue;
160
161 ++count;
162
163 }
164 return count;
165}
166
167} // namespace JSC
168