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