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 | #if ENABLE(JIT) |
29 | |
30 | #include "JSFunctionInlines.h" |
31 | #include "ObjectPropertyConditionSet.h" |
32 | #include "PolyProtoAccessChain.h" |
33 | #include <wtf/CommaPrinter.h> |
34 | |
35 | namespace JSC { |
36 | |
37 | struct AccessGenerationState; |
38 | |
39 | // An AccessCase describes one of the cases of a PolymorphicAccess. A PolymorphicAccess represents a |
40 | // planned (to generate in future) or generated stub for some inline cache. That stub contains fast |
41 | // path code for some finite number of fast cases, each described by an AccessCase object. |
42 | // |
43 | // An AccessCase object has a lifecycle that proceeds through several states. Note that the states |
44 | // of AccessCase have a lot to do with the global effect epoch (we'll say epoch for short). This is |
45 | // a simple way of reasoning about the state of the system outside this AccessCase. Any observable |
46 | // effect - like storing to a property, changing an object's structure, etc. - increments the epoch. |
47 | // The states are: |
48 | // |
49 | // Primordial: This is an AccessCase that was just allocated. It does not correspond to any actual |
50 | // code and it is not owned by any PolymorphicAccess. In this state, the AccessCase |
51 | // assumes that it is in the same epoch as when it was created. This is important |
52 | // because it may make claims about itself ("I represent a valid case so long as you |
53 | // register a watchpoint on this set") that could be contradicted by some outside |
54 | // effects (like firing and deleting the watchpoint set in question). This is also the |
55 | // state that an AccessCase is in when it is cloned (AccessCase::clone()). |
56 | // |
57 | // Committed: This happens as soon as some PolymorphicAccess takes ownership of this AccessCase. |
58 | // In this state, the AccessCase no longer assumes anything about the epoch. To |
59 | // accomplish this, PolymorphicAccess calls AccessCase::commit(). This must be done |
60 | // during the same epoch when the AccessCase was created, either by the client or by |
61 | // clone(). When created by the client, committing during the same epoch works because |
62 | // we can be sure that whatever watchpoint sets they spoke of are still valid. When |
63 | // created by clone(), we can be sure that the set is still valid because the original |
64 | // of the clone still has watchpoints on it. |
65 | // |
66 | // Generated: This is the state when the PolymorphicAccess generates code for this case by |
67 | // calling AccessCase::generate() or AccessCase::generateWithGuard(). At this point |
68 | // the case object will have some extra stuff in it, like possibly the CallLinkInfo |
69 | // object associated with the inline cache. |
70 | // FIXME: Moving into the Generated state should not mutate the AccessCase object or |
71 | // put more stuff into it. If we fix this, then we can get rid of AccessCase::clone(). |
72 | // https://bugs.webkit.org/show_bug.cgi?id=156456 |
73 | // |
74 | // An AccessCase may be destroyed while in any of these states. |
75 | // |
76 | // We will sometimes buffer committed AccessCases in the PolymorphicAccess object before generating |
77 | // code. This allows us to only regenerate once we've accumulated (hopefully) more than one new |
78 | // AccessCase. |
79 | class AccessCase { |
80 | WTF_MAKE_FAST_ALLOCATED; |
81 | public: |
82 | enum AccessType : uint8_t { |
83 | Load, |
84 | Transition, |
85 | Replace, |
86 | Miss, |
87 | GetGetter, |
88 | Getter, |
89 | Setter, |
90 | CustomValueGetter, |
91 | CustomAccessorGetter, |
92 | CustomValueSetter, |
93 | CustomAccessorSetter, |
94 | IntrinsicGetter, |
95 | InHit, |
96 | InMiss, |
97 | ArrayLength, |
98 | StringLength, |
99 | DirectArgumentsLength, |
100 | ScopedArgumentsLength, |
101 | ModuleNamespaceLoad, |
102 | InstanceOfHit, |
103 | InstanceOfMiss, |
104 | InstanceOfGeneric |
105 | }; |
106 | |
107 | enum State : uint8_t { |
108 | Primordial, |
109 | Committed, |
110 | Generated |
111 | }; |
112 | |
113 | template<typename T> |
114 | T& as() { return *static_cast<T*>(this); } |
115 | |
116 | template<typename T> |
117 | const T& as() const { return *static_cast<const T*>(this); } |
118 | |
119 | |
120 | template<typename AccessCaseType, typename... Arguments> |
121 | static std::unique_ptr<AccessCaseType> create(Arguments... arguments) |
122 | { |
123 | return std::unique_ptr<AccessCaseType>(new AccessCaseType(arguments...)); |
124 | } |
125 | |
126 | static std::unique_ptr<AccessCase> create(VM&, JSCell* owner, AccessType, PropertyOffset = invalidOffset, |
127 | Structure* = nullptr, const ObjectPropertyConditionSet& = ObjectPropertyConditionSet(), std::unique_ptr<PolyProtoAccessChain> = nullptr); |
128 | |
129 | // This create method should be used for transitions. |
130 | static std::unique_ptr<AccessCase> create(VM&, JSCell* owner, PropertyOffset, Structure* oldStructure, |
131 | Structure* newStructure, const ObjectPropertyConditionSet&, std::unique_ptr<PolyProtoAccessChain>); |
132 | |
133 | static std::unique_ptr<AccessCase> fromStructureStubInfo(VM&, JSCell* owner, StructureStubInfo&); |
134 | |
135 | AccessType type() const { return m_type; } |
136 | State state() const { return m_state; } |
137 | PropertyOffset offset() const { return m_offset; } |
138 | |
139 | Structure* structure() const |
140 | { |
141 | if (m_type == Transition) |
142 | return m_structure->previousID(); |
143 | return m_structure.get(); |
144 | } |
145 | bool guardedByStructureCheck() const; |
146 | |
147 | Structure* newStructure() const |
148 | { |
149 | ASSERT(m_type == Transition); |
150 | return m_structure.get(); |
151 | } |
152 | |
153 | ObjectPropertyConditionSet conditionSet() const { return m_conditionSet; } |
154 | |
155 | virtual bool hasAlternateBase() const; |
156 | virtual JSObject* alternateBase() const; |
157 | |
158 | virtual WatchpointSet* additionalSet() const { return nullptr; } |
159 | bool viaProxy() const { return m_viaProxy; } |
160 | |
161 | // If you supply the optional vector, this will append the set of cells that this will need to keep alive |
162 | // past the call. |
163 | bool doesCalls(Vector<JSCell*>* cellsToMark = nullptr) const; |
164 | |
165 | bool isGetter() const |
166 | { |
167 | switch (type()) { |
168 | case Getter: |
169 | case CustomValueGetter: |
170 | case CustomAccessorGetter: |
171 | return true; |
172 | default: |
173 | return false; |
174 | } |
175 | } |
176 | |
177 | bool isAccessor() const { return isGetter() || type() == Setter; } |
178 | |
179 | // Is it still possible for this case to ever be taken? Must call this as a prerequisite for |
180 | // calling generate() and friends. If this returns true, then you can call generate(). If |
181 | // this returns false, then generate() will crash. You must call generate() in the same epoch |
182 | // as when you called couldStillSucceed(). |
183 | bool couldStillSucceed() const; |
184 | |
185 | // If this method returns true, then it's a good idea to remove 'other' from the access once 'this' |
186 | // is added. This method assumes that in case of contradictions, 'this' represents a newer, and so |
187 | // more useful, truth. This method can be conservative; it will return false when it doubt. |
188 | bool canReplace(const AccessCase& other) const; |
189 | |
190 | void dump(PrintStream& out) const; |
191 | virtual void dumpImpl(PrintStream&, CommaPrinter&) const { } |
192 | |
193 | virtual ~AccessCase(); |
194 | |
195 | bool usesPolyProto() const |
196 | { |
197 | return !!m_polyProtoAccessChain; |
198 | } |
199 | |
200 | protected: |
201 | AccessCase(VM&, JSCell* owner, AccessType, PropertyOffset, Structure*, const ObjectPropertyConditionSet&, std::unique_ptr<PolyProtoAccessChain>); |
202 | AccessCase(AccessCase&&) = default; |
203 | AccessCase(const AccessCase& other) |
204 | : m_type(other.m_type) |
205 | , m_state(other.m_state) |
206 | , m_viaProxy(other.m_viaProxy) |
207 | , m_offset(other.m_offset) |
208 | , m_structure(other.m_structure) |
209 | , m_conditionSet(other.m_conditionSet) |
210 | { |
211 | if (other.m_polyProtoAccessChain) |
212 | m_polyProtoAccessChain = other.m_polyProtoAccessChain->clone(); |
213 | } |
214 | |
215 | AccessCase& operator=(const AccessCase&) = delete; |
216 | void resetState() { m_state = Primordial; } |
217 | |
218 | private: |
219 | friend class CodeBlock; |
220 | friend class PolymorphicAccess; |
221 | |
222 | bool visitWeak(VM&) const; |
223 | bool propagateTransitions(SlotVisitor&) const; |
224 | |
225 | // FIXME: This only exists because of how AccessCase puts post-generation things into itself. |
226 | // https://bugs.webkit.org/show_bug.cgi?id=156456 |
227 | virtual std::unique_ptr<AccessCase> clone() const; |
228 | |
229 | // Perform any action that must be performed before the end of the epoch in which the case |
230 | // was created. Returns a set of watchpoint sets that will need to be watched. |
231 | Vector<WatchpointSet*, 2> commit(VM&, const Identifier&); |
232 | |
233 | // Fall through on success. Two kinds of failures are supported: fall-through, which means that we |
234 | // should try a different case; and failure, which means that this was the right case but it needs |
235 | // help from the slow path. |
236 | void generateWithGuard(AccessGenerationState&, MacroAssembler::JumpList& fallThrough); |
237 | |
238 | // Fall through on success, add a jump to the failure list on failure. |
239 | void generate(AccessGenerationState&); |
240 | |
241 | void generateImpl(AccessGenerationState&); |
242 | |
243 | AccessType m_type; |
244 | State m_state { Primordial }; |
245 | protected: |
246 | // m_viaProxy is true only if the instance inherits (or it is) ProxyableAccessCase. |
247 | // We put this value here instead of ProxyableAccessCase to reduce the size of ProxyableAccessCase and its |
248 | // derived classes, which are super frequently allocated. |
249 | bool m_viaProxy { false }; |
250 | private: |
251 | PropertyOffset m_offset; |
252 | |
253 | // Usually this is the structure that we expect the base object to have. But, this is the *new* |
254 | // structure for a transition and we rely on the fact that it has a strong reference to the old |
255 | // structure. For proxies, this is the structure of the object behind the proxy. |
256 | WriteBarrier<Structure> m_structure; |
257 | |
258 | ObjectPropertyConditionSet m_conditionSet; |
259 | |
260 | std::unique_ptr<PolyProtoAccessChain> m_polyProtoAccessChain; |
261 | }; |
262 | |
263 | } // namespace JSC |
264 | |
265 | #endif |
266 | |