1 | /* |
2 | * Copyright (C) 2015-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 "JSCast.h" |
29 | #include "VM.h" |
30 | #include "Watchpoint.h" |
31 | #include "WriteBarrier.h" |
32 | #include <wtf/Nonmovable.h> |
33 | |
34 | namespace JSC { |
35 | |
36 | template<typename JSCellType> |
37 | class InferredValue { |
38 | WTF_MAKE_NONCOPYABLE(InferredValue); |
39 | WTF_MAKE_NONMOVABLE(InferredValue); |
40 | public: |
41 | // For the purpose of deciding whether or not to watch this variable, you only need |
42 | // to inspect inferredValue(). If this returns something other than the empty |
43 | // value, then it means that at all future safepoints, this watchpoint set will be |
44 | // in one of these states: |
45 | // |
46 | // IsWatched: in this case, the variable's value must still be the |
47 | // inferredValue. |
48 | // |
49 | // IsInvalidated: in this case the variable's value may be anything but you'll |
50 | // either notice that it's invalidated and not install the watchpoint, or |
51 | // you will have been notified that the watchpoint was fired. |
52 | JSCellType* inferredValue() |
53 | { |
54 | uintptr_t data = m_data; |
55 | if (isFat(data)) |
56 | return fat(data)->inferredValue(); |
57 | return bitwise_cast<JSCellType*>(data & ValueMask); |
58 | } |
59 | |
60 | explicit InferredValue() |
61 | : m_data(encodeState(ClearWatchpoint)) |
62 | { |
63 | ASSERT(inferredValue() == nullptr); |
64 | } |
65 | |
66 | ~InferredValue() |
67 | { |
68 | if (isThin()) |
69 | return; |
70 | freeFat(); |
71 | } |
72 | |
73 | // Fast way of getting the state, which only works from the main thread. |
74 | WatchpointState stateOnJSThread() const |
75 | { |
76 | uintptr_t data = m_data; |
77 | if (isFat(data)) |
78 | return fat(data)->stateOnJSThread(); |
79 | return decodeState(data); |
80 | } |
81 | |
82 | // It is safe to call this from another thread. It may return a prior state, |
83 | // but that should be fine since you should only perform actions based on the |
84 | // state if you also add a watchpoint. |
85 | WatchpointState state() const |
86 | { |
87 | WTF::loadLoadFence(); |
88 | uintptr_t data = m_data; |
89 | WTF::loadLoadFence(); |
90 | if (isFat(data)) |
91 | return fat(data)->state(); |
92 | return decodeState(data); |
93 | } |
94 | |
95 | // It is safe to call this from another thread. It may return false |
96 | // even if the set actually had been invalidated, but that ought to happen |
97 | // only in the case of races, and should be rare. |
98 | bool hasBeenInvalidated() const |
99 | { |
100 | return state() == IsInvalidated; |
101 | } |
102 | |
103 | // Like hasBeenInvalidated(), may be called from another thread. |
104 | bool isStillValid() const |
105 | { |
106 | return !hasBeenInvalidated(); |
107 | } |
108 | |
109 | void add(Watchpoint*); |
110 | |
111 | void invalidate(VM& vm, const FireDetail& detail) |
112 | { |
113 | if (isFat()) |
114 | fat()->invalidate(vm, detail); |
115 | else |
116 | m_data = encodeState(IsInvalidated); |
117 | } |
118 | |
119 | bool isBeingWatched() const |
120 | { |
121 | if (isFat()) |
122 | return fat()->isBeingWatched(); |
123 | return false; |
124 | } |
125 | |
126 | void notifyWrite(VM& vm, JSCell* owner, JSCellType* value, const FireDetail& detail) |
127 | { |
128 | if (LIKELY(stateOnJSThread() == IsInvalidated)) |
129 | return; |
130 | notifyWriteSlow(vm, owner, value, detail); |
131 | } |
132 | |
133 | void notifyWrite(VM& vm, JSCell* owner, JSCellType* value, const char* reason) |
134 | { |
135 | if (LIKELY(stateOnJSThread() == IsInvalidated)) |
136 | return; |
137 | notifyWriteSlow(vm, owner, value, reason); |
138 | } |
139 | |
140 | void finalizeUnconditionally(VM&); |
141 | |
142 | private: |
143 | class InferredValueWatchpointSet final : public WatchpointSet { |
144 | public: |
145 | InferredValueWatchpointSet(WatchpointState state, JSCellType* value) |
146 | : WatchpointSet(state) |
147 | , m_value(value) |
148 | { |
149 | } |
150 | |
151 | JSCellType* inferredValue() const { return m_value; } |
152 | |
153 | void invalidate(VM& vm, const FireDetail& detail) |
154 | { |
155 | m_value = nullptr; |
156 | WatchpointSet::invalidate(vm, detail); |
157 | } |
158 | |
159 | void notifyWriteSlow(VM&, JSCell* owner, JSCellType*, const FireDetail&); |
160 | |
161 | private: |
162 | JSCellType* m_value; |
163 | }; |
164 | |
165 | static constexpr uintptr_t IsThinFlag = 1; |
166 | static constexpr uintptr_t StateMask = 6; |
167 | static constexpr uintptr_t StateShift = 1; |
168 | static constexpr uintptr_t ValueMask = ~static_cast<uintptr_t>(IsThinFlag | StateMask); |
169 | |
170 | static bool isThin(uintptr_t data) { return data & IsThinFlag; } |
171 | static bool isFat(uintptr_t data) { return !isThin(data); } |
172 | |
173 | static WatchpointState decodeState(uintptr_t data) |
174 | { |
175 | ASSERT(isThin(data)); |
176 | return static_cast<WatchpointState>((data & StateMask) >> StateShift); |
177 | } |
178 | |
179 | static uintptr_t encodeState(WatchpointState state) |
180 | { |
181 | return (static_cast<uintptr_t>(state) << StateShift) | IsThinFlag; |
182 | } |
183 | |
184 | bool isThin() const { return isThin(m_data); } |
185 | bool isFat() const { return isFat(m_data); }; |
186 | |
187 | static InferredValueWatchpointSet* fat(uintptr_t data) |
188 | { |
189 | return bitwise_cast<InferredValueWatchpointSet*>(data); |
190 | } |
191 | |
192 | InferredValueWatchpointSet* fat() |
193 | { |
194 | ASSERT(isFat()); |
195 | return fat(m_data); |
196 | } |
197 | |
198 | const InferredValueWatchpointSet* fat() const |
199 | { |
200 | ASSERT(isFat()); |
201 | return fat(m_data); |
202 | } |
203 | |
204 | InferredValueWatchpointSet* inflate() |
205 | { |
206 | if (LIKELY(isFat())) |
207 | return fat(); |
208 | return inflateSlow(); |
209 | } |
210 | |
211 | InferredValueWatchpointSet* inflateSlow(); |
212 | void freeFat(); |
213 | |
214 | void notifyWriteSlow(VM&, JSCell* owner, JSCellType*, const FireDetail&); |
215 | void notifyWriteSlow(VM&, JSCell* owner, JSCellType*, const char* reason); |
216 | |
217 | uintptr_t m_data; |
218 | }; |
219 | |
220 | template<typename JSCellType> |
221 | void InferredValue<JSCellType>::InferredValueWatchpointSet::notifyWriteSlow(VM& vm, JSCell* owner, JSCellType* value, const FireDetail& detail) |
222 | { |
223 | switch (state()) { |
224 | case ClearWatchpoint: |
225 | m_value = value; |
226 | vm.heap.writeBarrier(owner, value); |
227 | startWatching(); |
228 | return; |
229 | |
230 | case IsWatched: |
231 | ASSERT(!!m_value); |
232 | if (m_value == value) |
233 | return; |
234 | invalidate(vm, detail); |
235 | return; |
236 | |
237 | case IsInvalidated: |
238 | ASSERT_NOT_REACHED(); |
239 | return; |
240 | } |
241 | |
242 | ASSERT_NOT_REACHED(); |
243 | } |
244 | |
245 | template<typename JSCellType> |
246 | void InferredValue<JSCellType>::notifyWriteSlow(VM& vm, JSCell* owner, JSCellType* value, const FireDetail& detail) |
247 | { |
248 | uintptr_t data = m_data; |
249 | if (isFat(data)) { |
250 | fat(data)->notifyWriteSlow(vm, owner, value, detail); |
251 | return; |
252 | } |
253 | |
254 | switch (state()) { |
255 | case ClearWatchpoint: |
256 | ASSERT(decodeState(m_data) != IsInvalidated); |
257 | m_data = (bitwise_cast<uintptr_t>(value) & ValueMask) | encodeState(IsWatched); |
258 | vm.heap.writeBarrier(owner, value); |
259 | return; |
260 | |
261 | case IsWatched: |
262 | ASSERT(!!inferredValue()); |
263 | if (inferredValue() == value) |
264 | return; |
265 | invalidate(vm, detail); |
266 | return; |
267 | |
268 | case IsInvalidated: |
269 | ASSERT_NOT_REACHED(); |
270 | return; |
271 | } |
272 | |
273 | ASSERT_NOT_REACHED(); |
274 | } |
275 | |
276 | template<typename JSCellType> |
277 | void InferredValue<JSCellType>::notifyWriteSlow(VM& vm, JSCell* owner, JSCellType* value, const char* reason) |
278 | { |
279 | notifyWriteSlow(vm, owner, value, StringFireDetail(reason)); |
280 | } |
281 | |
282 | template<typename JSCellType> |
283 | void InferredValue<JSCellType>::add(Watchpoint* watchpoint) |
284 | { |
285 | inflate()->add(watchpoint); |
286 | } |
287 | |
288 | template<typename JSCellType> |
289 | auto InferredValue<JSCellType>::inflateSlow() -> InferredValueWatchpointSet* |
290 | { |
291 | ASSERT(isThin()); |
292 | ASSERT(!isCompilationThread()); |
293 | uintptr_t data = m_data; |
294 | InferredValueWatchpointSet* fat = adoptRef(new InferredValueWatchpointSet(decodeState(m_data), bitwise_cast<JSCellType*>(data & ValueMask))).leakRef(); |
295 | WTF::storeStoreFence(); |
296 | m_data = bitwise_cast<uintptr_t>(fat); |
297 | return fat; |
298 | } |
299 | |
300 | template<typename JSCellType> |
301 | void InferredValue<JSCellType>::freeFat() |
302 | { |
303 | ASSERT(isFat()); |
304 | fat()->deref(); |
305 | } |
306 | |
307 | } // namespace JSC |
308 | |