DFG should have adaptive structure watchpoints
[WebKit-https.git] / Source / JavaScriptCore / bytecode / PutByIdStatus.cpp
1 /*
2  * Copyright (C) 2012-2015 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 #include "config.h"
27 #include "PutByIdStatus.h"
28
29 #include "AccessorCallJITStubRoutine.h"
30 #include "CodeBlock.h"
31 #include "ComplexGetStatus.h"
32 #include "LLIntData.h"
33 #include "LowLevelInterpreter.h"
34 #include "JSCInlines.h"
35 #include "PolymorphicPutByIdList.h"
36 #include "Structure.h"
37 #include "StructureChain.h"
38 #include <wtf/ListDump.h>
39
40 namespace JSC {
41
42 bool PutByIdStatus::appendVariant(const PutByIdVariant& variant)
43 {
44     for (unsigned i = 0; i < m_variants.size(); ++i) {
45         if (m_variants[i].attemptToMerge(variant))
46             return true;
47     }
48     for (unsigned i = 0; i < m_variants.size(); ++i) {
49         if (m_variants[i].oldStructure().overlaps(variant.oldStructure()))
50             return false;
51     }
52     m_variants.append(variant);
53     return true;
54 }
55
56 #if ENABLE(DFG_JIT)
57 bool PutByIdStatus::hasExitSite(const ConcurrentJITLocker& locker, CodeBlock* profiledBlock, unsigned bytecodeIndex)
58 {
59     return profiledBlock->hasExitSite(locker, DFG::FrequentExitSite(bytecodeIndex, BadCache))
60         || profiledBlock->hasExitSite(locker, DFG::FrequentExitSite(bytecodeIndex, BadConstantCache));
61     
62 }
63 #endif
64
65 PutByIdStatus PutByIdStatus::computeFromLLInt(CodeBlock* profiledBlock, unsigned bytecodeIndex, UniquedStringImpl* uid)
66 {
67     UNUSED_PARAM(profiledBlock);
68     UNUSED_PARAM(bytecodeIndex);
69     UNUSED_PARAM(uid);
70     Instruction* instruction = profiledBlock->instructions().begin() + bytecodeIndex;
71
72     Structure* structure = instruction[4].u.structure.get();
73     if (!structure)
74         return PutByIdStatus(NoInformation);
75     
76     if (instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id)
77         || instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_out_of_line)) {
78         PropertyOffset offset = structure->getConcurrently(uid);
79         if (!isValidOffset(offset))
80             return PutByIdStatus(NoInformation);
81         
82         return PutByIdVariant::replace(structure, offset);
83     }
84     
85     ASSERT(structure->transitionWatchpointSetHasBeenInvalidated());
86     
87     ASSERT(instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_transition_direct)
88         || instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_transition_normal)
89         || instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_transition_direct_out_of_line)
90         || instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_transition_normal_out_of_line));
91     
92     Structure* newStructure = instruction[6].u.structure.get();
93     
94     PropertyOffset offset = newStructure->getConcurrently(uid);
95     if (!isValidOffset(offset))
96         return PutByIdStatus(NoInformation);
97     
98     ObjectPropertyConditionSet conditionSet;
99     if (instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_transition_normal)
100         || instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_transition_normal_out_of_line)) {
101         conditionSet =
102             generateConditionsForPropertySetterMissConcurrently(
103                 *profiledBlock->vm(), profiledBlock->globalObject(), structure, uid);
104         if (!conditionSet.isValid())
105             return PutByIdStatus(NoInformation);
106     }
107     
108     return PutByIdVariant::transition(structure, newStructure, conditionSet, offset);
109 }
110
111 PutByIdStatus PutByIdStatus::computeFor(CodeBlock* profiledBlock, StubInfoMap& map, unsigned bytecodeIndex, UniquedStringImpl* uid)
112 {
113     ConcurrentJITLocker locker(profiledBlock->m_lock);
114     
115     UNUSED_PARAM(profiledBlock);
116     UNUSED_PARAM(bytecodeIndex);
117     UNUSED_PARAM(uid);
118 #if ENABLE(DFG_JIT)
119     if (hasExitSite(locker, profiledBlock, bytecodeIndex))
120         return PutByIdStatus(TakesSlowPath);
121     
122     StructureStubInfo* stubInfo = map.get(CodeOrigin(bytecodeIndex));
123     PutByIdStatus result = computeForStubInfo(
124         locker, profiledBlock, stubInfo, uid,
125         CallLinkStatus::computeExitSiteData(locker, profiledBlock, bytecodeIndex));
126     if (!result)
127         return computeFromLLInt(profiledBlock, bytecodeIndex, uid);
128     
129     return result;
130 #else // ENABLE(JIT)
131     UNUSED_PARAM(map);
132     return PutByIdStatus(NoInformation);
133 #endif // ENABLE(JIT)
134 }
135
136 #if ENABLE(JIT)
137 PutByIdStatus PutByIdStatus::computeForStubInfo(
138     const ConcurrentJITLocker& locker, CodeBlock* profiledBlock, StructureStubInfo* stubInfo,
139     UniquedStringImpl* uid, CallLinkStatus::ExitSiteData callExitSiteData)
140 {
141     if (!stubInfo)
142         return PutByIdStatus();
143     
144     if (stubInfo->tookSlowPath)
145         return PutByIdStatus(TakesSlowPath);
146     
147     if (!stubInfo->seen)
148         return PutByIdStatus();
149     
150     switch (stubInfo->accessType) {
151     case access_unset:
152         // If the JIT saw it but didn't optimize it, then assume that this takes slow path.
153         return PutByIdStatus(TakesSlowPath);
154         
155     case access_put_by_id_replace: {
156         PropertyOffset offset =
157             stubInfo->u.putByIdReplace.baseObjectStructure->getConcurrently(uid);
158         if (isValidOffset(offset)) {
159             return PutByIdVariant::replace(
160                 stubInfo->u.putByIdReplace.baseObjectStructure.get(), offset);
161         }
162         return PutByIdStatus(TakesSlowPath);
163     }
164         
165     case access_put_by_id_transition_normal:
166     case access_put_by_id_transition_direct: {
167         ASSERT(stubInfo->u.putByIdTransition.previousStructure->transitionWatchpointSetHasBeenInvalidated());
168         PropertyOffset offset = 
169             stubInfo->u.putByIdTransition.structure->getConcurrently(uid);
170         if (isValidOffset(offset)) {
171             ObjectPropertyConditionSet conditionSet = ObjectPropertyConditionSet::fromRawPointer(
172                 stubInfo->u.putByIdTransition.rawConditionSet);
173             if (!conditionSet.structuresEnsureValidity())
174                 return PutByIdStatus(TakesSlowPath);
175             return PutByIdVariant::transition(
176                 stubInfo->u.putByIdTransition.previousStructure.get(),
177                 stubInfo->u.putByIdTransition.structure.get(),
178                 conditionSet, offset);
179         }
180         return PutByIdStatus(TakesSlowPath);
181     }
182         
183     case access_put_by_id_list: {
184         PolymorphicPutByIdList* list = stubInfo->u.putByIdList.list;
185         
186         PutByIdStatus result;
187         result.m_state = Simple;
188         
189         State slowPathState = TakesSlowPath;
190         for (unsigned i = 0; i < list->size(); ++i) {
191             const PutByIdAccess& access = list->at(i);
192             
193             switch (access.type()) {
194             case PutByIdAccess::Setter:
195             case PutByIdAccess::CustomSetter:
196                 slowPathState = MakesCalls;
197                 break;
198             default:
199                 break;
200             }
201         }
202         
203         for (unsigned i = 0; i < list->size(); ++i) {
204             const PutByIdAccess& access = list->at(i);
205             
206             PutByIdVariant variant;
207             
208             switch (access.type()) {
209             case PutByIdAccess::Replace: {
210                 Structure* structure = access.structure();
211                 PropertyOffset offset = structure->getConcurrently(uid);
212                 if (!isValidOffset(offset))
213                     return PutByIdStatus(slowPathState);
214                 variant = PutByIdVariant::replace(structure, offset);
215                 break;
216             }
217                 
218             case PutByIdAccess::Transition: {
219                 PropertyOffset offset =
220                     access.newStructure()->getConcurrently(uid);
221                 if (!isValidOffset(offset))
222                     return PutByIdStatus(slowPathState);
223                 ObjectPropertyConditionSet conditionSet = access.conditionSet();
224                 if (!conditionSet.structuresEnsureValidity())
225                     return PutByIdStatus(slowPathState);
226                 variant = PutByIdVariant::transition(
227                     access.oldStructure(), access.newStructure(), conditionSet, offset);
228                 break;
229             }
230                 
231             case PutByIdAccess::Setter: {
232                 Structure* structure = access.structure();
233                 
234                 ComplexGetStatus complexGetStatus = ComplexGetStatus::computeFor(
235                     structure, access.conditionSet(), uid);
236                 
237                 switch (complexGetStatus.kind()) {
238                 case ComplexGetStatus::ShouldSkip:
239                     continue;
240                     
241                 case ComplexGetStatus::TakesSlowPath:
242                     return PutByIdStatus(slowPathState);
243                     
244                 case ComplexGetStatus::Inlineable: {
245                     AccessorCallJITStubRoutine* stub = static_cast<AccessorCallJITStubRoutine*>(
246                         access.stubRoutine());
247                     std::unique_ptr<CallLinkStatus> callLinkStatus =
248                         std::make_unique<CallLinkStatus>(
249                             CallLinkStatus::computeFor(
250                                 locker, profiledBlock, *stub->m_callLinkInfo, callExitSiteData));
251                     
252                     variant = PutByIdVariant::setter(
253                         structure, complexGetStatus.offset(), complexGetStatus.conditionSet(),
254                         WTF::move(callLinkStatus));
255                 } }
256                 break;
257             }
258                 
259             case PutByIdAccess::CustomSetter:
260                 return PutByIdStatus(MakesCalls);
261
262             default:
263                 return PutByIdStatus(slowPathState);
264             }
265             
266             if (!result.appendVariant(variant))
267                 return PutByIdStatus(slowPathState);
268         }
269         
270         return result;
271     }
272         
273     default:
274         return PutByIdStatus(TakesSlowPath);
275     }
276 }
277 #endif
278
279 PutByIdStatus PutByIdStatus::computeFor(CodeBlock* baselineBlock, CodeBlock* dfgBlock, StubInfoMap& baselineMap, StubInfoMap& dfgMap, CodeOrigin codeOrigin, UniquedStringImpl* uid)
280 {
281 #if ENABLE(DFG_JIT)
282     if (dfgBlock) {
283         CallLinkStatus::ExitSiteData exitSiteData;
284         {
285             ConcurrentJITLocker locker(baselineBlock->m_lock);
286             if (hasExitSite(locker, baselineBlock, codeOrigin.bytecodeIndex))
287                 return PutByIdStatus(TakesSlowPath);
288             exitSiteData = CallLinkStatus::computeExitSiteData(
289                 locker, baselineBlock, codeOrigin.bytecodeIndex);
290         }
291             
292         PutByIdStatus result;
293         {
294             ConcurrentJITLocker locker(dfgBlock->m_lock);
295             result = computeForStubInfo(
296                 locker, dfgBlock, dfgMap.get(codeOrigin), uid, exitSiteData);
297         }
298         
299         // We use TakesSlowPath in some cases where the stub was unset. That's weird and
300         // it would be better not to do that. But it means that we have to defend
301         // ourselves here.
302         if (result.isSimple())
303             return result;
304     }
305 #else
306     UNUSED_PARAM(dfgBlock);
307     UNUSED_PARAM(dfgMap);
308 #endif
309
310     return computeFor(baselineBlock, baselineMap, codeOrigin.bytecodeIndex, uid);
311 }
312
313 PutByIdStatus PutByIdStatus::computeFor(JSGlobalObject* globalObject, const StructureSet& set, UniquedStringImpl* uid, bool isDirect)
314 {
315     if (parseIndex(*uid))
316         return PutByIdStatus(TakesSlowPath);
317
318     if (set.isEmpty())
319         return PutByIdStatus();
320     
321     PutByIdStatus result;
322     result.m_state = Simple;
323     for (unsigned i = 0; i < set.size(); ++i) {
324         Structure* structure = set[i];
325         
326         if (structure->typeInfo().overridesGetOwnPropertySlot() && structure->typeInfo().type() != GlobalObjectType)
327             return PutByIdStatus(TakesSlowPath);
328
329         if (!structure->propertyAccessesAreCacheable())
330             return PutByIdStatus(TakesSlowPath);
331     
332         unsigned attributes;
333         PropertyOffset offset = structure->getConcurrently(uid, attributes);
334         if (isValidOffset(offset)) {
335             if (attributes & CustomAccessor)
336                 return PutByIdStatus(MakesCalls);
337
338             if (attributes & (Accessor | ReadOnly))
339                 return PutByIdStatus(TakesSlowPath);
340             
341             WatchpointSet* replaceSet = structure->propertyReplacementWatchpointSet(offset);
342             if (!replaceSet || replaceSet->isStillValid()) {
343                 // When this executes, it'll create, and fire, this replacement watchpoint set.
344                 // That means that  this has probably never executed or that something fishy is
345                 // going on. Also, we cannot create or fire the watchpoint set from the concurrent
346                 // JIT thread, so even if we wanted to do this, we'd need to have a lazy thingy.
347                 // So, better leave this alone and take slow path.
348                 return PutByIdStatus(TakesSlowPath);
349             }
350             
351             if (!result.appendVariant(PutByIdVariant::replace(structure, offset)))
352                 return PutByIdStatus(TakesSlowPath);
353             continue;
354         }
355     
356         // Our hypothesis is that we're doing a transition. Before we prove that this is really
357         // true, we want to do some sanity checks.
358     
359         // Don't cache put transitions on dictionaries.
360         if (structure->isDictionary())
361             return PutByIdStatus(TakesSlowPath);
362
363         // If the structure corresponds to something that isn't an object, then give up, since
364         // we don't want to be adding properties to strings.
365         if (!structure->typeInfo().isObject())
366             return PutByIdStatus(TakesSlowPath);
367     
368         ObjectPropertyConditionSet conditionSet;
369         if (!isDirect) {
370             conditionSet = generateConditionsForPropertySetterMissConcurrently(
371                 globalObject->vm(), globalObject, structure, uid);
372             if (!conditionSet.isValid())
373                 return PutByIdStatus(TakesSlowPath);
374         }
375     
376         // We only optimize if there is already a structure that the transition is cached to.
377         Structure* transition = Structure::addPropertyTransitionToExistingStructureConcurrently(structure, uid, 0, offset);
378         if (!transition)
379             return PutByIdStatus(TakesSlowPath);
380         ASSERT(isValidOffset(offset));
381     
382         bool didAppend = result.appendVariant(
383             PutByIdVariant::transition(structure, transition, conditionSet, offset));
384         if (!didAppend)
385             return PutByIdStatus(TakesSlowPath);
386     }
387     
388     return result;
389 }
390
391 bool PutByIdStatus::makesCalls() const
392 {
393     if (m_state == MakesCalls)
394         return true;
395     
396     if (m_state != Simple)
397         return false;
398     
399     for (unsigned i = m_variants.size(); i--;) {
400         if (m_variants[i].makesCalls())
401             return true;
402     }
403     
404     return false;
405 }
406
407 void PutByIdStatus::dump(PrintStream& out) const
408 {
409     switch (m_state) {
410     case NoInformation:
411         out.print("(NoInformation)");
412         return;
413         
414     case Simple:
415         out.print("(", listDump(m_variants), ")");
416         return;
417         
418     case TakesSlowPath:
419         out.print("(TakesSlowPath)");
420         return;
421     case MakesCalls:
422         out.print("(MakesCalls)");
423         return;
424     }
425     
426     RELEASE_ASSERT_NOT_REACHED();
427 }
428
429 } // namespace JSC
430