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