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
34namespace JSC {
35
36template<typename JSCellType>
37class InferredValue {
38 WTF_MAKE_NONCOPYABLE(InferredValue);
39 WTF_MAKE_NONMOVABLE(InferredValue);
40public:
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
142private:
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
220template<typename JSCellType>
221void 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
245template<typename JSCellType>
246void 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
276template<typename JSCellType>
277void InferredValue<JSCellType>::notifyWriteSlow(VM& vm, JSCell* owner, JSCellType* value, const char* reason)
278{
279 notifyWriteSlow(vm, owner, value, StringFireDetail(reason));
280}
281
282template<typename JSCellType>
283void InferredValue<JSCellType>::add(Watchpoint* watchpoint)
284{
285 inflate()->add(watchpoint);
286}
287
288template<typename JSCellType>
289auto 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
300template<typename JSCellType>
301void InferredValue<JSCellType>::freeFat()
302{
303 ASSERT(isFat());
304 fat()->deref();
305}
306
307} // namespace JSC
308