We should support CreateThis in the FTL
[WebKit-https.git] / Source / JavaScriptCore / bytecode / GetByIdStatus.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 "GetByIdStatus.h"
28
29 #include "CodeBlock.h"
30 #include "ComplexGetStatus.h"
31 #include "GetterSetterAccessCase.h"
32 #include "ICStatusUtils.h"
33 #include "InterpreterInlines.h"
34 #include "IntrinsicGetterAccessCase.h"
35 #include "JSCInlines.h"
36 #include "JSScope.h"
37 #include "LLIntData.h"
38 #include "LowLevelInterpreter.h"
39 #include "ModuleNamespaceAccessCase.h"
40 #include "PolymorphicAccess.h"
41 #include "StructureStubInfo.h"
42 #include <wtf/ListDump.h>
43
44 namespace JSC {
45 namespace DOMJIT {
46 class GetterSetter;
47 }
48
49 bool GetByIdStatus::appendVariant(const GetByIdVariant& variant)
50 {
51     return appendICStatusVariant(m_variants, variant);
52 }
53
54 GetByIdStatus GetByIdStatus::computeFromLLInt(CodeBlock* profiledBlock, unsigned bytecodeIndex, UniquedStringImpl* uid)
55 {
56     VM& vm = *profiledBlock->vm();
57     
58     Instruction* instruction = &profiledBlock->instructions()[bytecodeIndex];
59
60     switch (Interpreter::getOpcodeID(instruction[0].u.opcode)) {
61     case op_get_by_id:
62     case op_get_by_id_direct: {
63         StructureID structureID = instruction[4].u.structureID;
64         if (!structureID)
65             return GetByIdStatus(NoInformation, false);
66
67         Structure* structure = vm.heap.structureIDTable().get(structureID);
68
69         if (structure->takesSlowPathInDFGForImpureProperty())
70             return GetByIdStatus(NoInformation, false);
71
72         unsigned attributes;
73         PropertyOffset offset = structure->getConcurrently(uid, attributes);
74         if (!isValidOffset(offset))
75             return GetByIdStatus(NoInformation, false);
76         if (attributes & PropertyAttribute::CustomAccessor)
77             return GetByIdStatus(NoInformation, false);
78
79         return GetByIdStatus(Simple, false, GetByIdVariant(StructureSet(structure), offset));
80     }
81
82     case op_get_array_length:
83     case op_try_get_by_id:
84     case op_get_by_id_proto_load:
85     case op_get_by_id_unset: {
86         // FIXME: We should not just bail if we see a try_get_by_id or a get_by_id_proto_load.
87         // https://bugs.webkit.org/show_bug.cgi?id=158039
88         return GetByIdStatus(NoInformation, false);
89     }
90
91     default: {
92         ASSERT_NOT_REACHED();
93         return GetByIdStatus(NoInformation, false);
94     }
95     }
96 }
97
98 GetByIdStatus GetByIdStatus::computeFor(CodeBlock* profiledBlock, ICStatusMap& map, unsigned bytecodeIndex, UniquedStringImpl* uid, ExitFlag didExit, CallLinkStatus::ExitSiteData callExitSiteData)
99 {
100     ConcurrentJSLocker locker(profiledBlock->m_lock);
101
102     GetByIdStatus result;
103
104 #if ENABLE(DFG_JIT)
105     result = computeForStubInfoWithoutExitSiteFeedback(
106         locker, profiledBlock, map.get(CodeOrigin(bytecodeIndex)).stubInfo, uid,
107         callExitSiteData);
108     
109     if (didExit)
110         return result.slowVersion();
111 #else
112     UNUSED_PARAM(map);
113 #endif
114
115     if (!result)
116         return computeFromLLInt(profiledBlock, bytecodeIndex, uid);
117     
118     return result;
119 }
120
121 #if ENABLE(DFG_JIT)
122 GetByIdStatus GetByIdStatus::computeForStubInfo(const ConcurrentJSLocker& locker, CodeBlock* profiledBlock, StructureStubInfo* stubInfo, CodeOrigin codeOrigin, UniquedStringImpl* uid)
123 {
124     GetByIdStatus result = GetByIdStatus::computeForStubInfoWithoutExitSiteFeedback(
125         locker, profiledBlock, stubInfo, uid,
126         CallLinkStatus::computeExitSiteData(profiledBlock, codeOrigin.bytecodeIndex));
127
128     if (!result.takesSlowPath() && hasBadCacheExitSite(profiledBlock, codeOrigin.bytecodeIndex))
129         return result.slowVersion();
130     return result;
131 }
132 #endif // ENABLE(DFG_JIT)
133
134 #if ENABLE(JIT)
135 GetByIdStatus::GetByIdStatus(const ModuleNamespaceAccessCase& accessCase)
136     : m_state(ModuleNamespace)
137     , m_wasSeenInJIT(true)
138     , m_moduleNamespaceObject(accessCase.moduleNamespaceObject())
139     , m_moduleEnvironment(accessCase.moduleEnvironment())
140     , m_scopeOffset(accessCase.scopeOffset())
141 {
142 }
143
144 GetByIdStatus GetByIdStatus::computeForStubInfoWithoutExitSiteFeedback(
145     const ConcurrentJSLocker& locker, CodeBlock* profiledBlock, StructureStubInfo* stubInfo, UniquedStringImpl* uid,
146     CallLinkStatus::ExitSiteData callExitSiteData)
147 {
148     StubInfoSummary summary = StructureStubInfo::summary(stubInfo);
149     if (!isInlineable(summary))
150         return GetByIdStatus(summary);
151     
152     // Finally figure out if we can derive an access strategy.
153     GetByIdStatus result;
154     result.m_state = Simple;
155     result.m_wasSeenInJIT = true; // This is interesting for bytecode dumping only.
156     switch (stubInfo->cacheType) {
157     case CacheType::Unset:
158         return GetByIdStatus(NoInformation);
159         
160     case CacheType::GetByIdSelf: {
161         Structure* structure = stubInfo->u.byIdSelf.baseObjectStructure.get();
162         if (structure->takesSlowPathInDFGForImpureProperty())
163             return GetByIdStatus(JSC::slowVersion(summary));
164         unsigned attributes;
165         GetByIdVariant variant;
166         variant.m_offset = structure->getConcurrently(uid, attributes);
167         if (!isValidOffset(variant.m_offset))
168             return GetByIdStatus(JSC::slowVersion(summary));
169         if (attributes & PropertyAttribute::CustomAccessor)
170             return GetByIdStatus(JSC::slowVersion(summary));
171         
172         variant.m_structureSet.add(structure);
173         bool didAppend = result.appendVariant(variant);
174         ASSERT_UNUSED(didAppend, didAppend);
175         return result;
176     }
177         
178     case CacheType::Stub: {
179         PolymorphicAccess* list = stubInfo->u.stub;
180         if (list->size() == 1) {
181             const AccessCase& access = list->at(0);
182             switch (access.type()) {
183             case AccessCase::ModuleNamespaceLoad:
184                 return GetByIdStatus(access.as<ModuleNamespaceAccessCase>());
185             default:
186                 break;
187             }
188         }
189
190         for (unsigned listIndex = 0; listIndex < list->size(); ++listIndex) {
191             const AccessCase& access = list->at(listIndex);
192             if (access.viaProxy())
193                 return GetByIdStatus(JSC::slowVersion(summary));
194
195             if (access.usesPolyProto())
196                 return GetByIdStatus(JSC::slowVersion(summary));
197             
198             Structure* structure = access.structure();
199             if (!structure) {
200                 // The null structure cases arise due to array.length and string.length. We have no way
201                 // of creating a GetByIdVariant for those, and we don't really have to since the DFG
202                 // handles those cases in FixupPhase using value profiling. That's a bit awkward - we
203                 // shouldn't have to use value profiling to discover something that the AccessCase
204                 // could have told us. But, it works well enough. So, our only concern here is to not
205                 // crash on null structure.
206                 return GetByIdStatus(JSC::slowVersion(summary));
207             }
208             
209             ComplexGetStatus complexGetStatus = ComplexGetStatus::computeFor(
210                 structure, access.conditionSet(), uid);
211              
212             switch (complexGetStatus.kind()) {
213             case ComplexGetStatus::ShouldSkip:
214                 continue;
215                  
216             case ComplexGetStatus::TakesSlowPath:
217                 return GetByIdStatus(JSC::slowVersion(summary));
218                  
219             case ComplexGetStatus::Inlineable: {
220                 std::unique_ptr<CallLinkStatus> callLinkStatus;
221                 JSFunction* intrinsicFunction = nullptr;
222                 FunctionPtr<OperationPtrTag> customAccessorGetter;
223                 std::optional<DOMAttributeAnnotation> domAttribute;
224
225                 switch (access.type()) {
226                 case AccessCase::Load:
227                 case AccessCase::GetGetter:
228                 case AccessCase::Miss: {
229                     break;
230                 }
231                 case AccessCase::IntrinsicGetter: {
232                     intrinsicFunction = access.as<IntrinsicGetterAccessCase>().intrinsicFunction();
233                     break;
234                 }
235                 case AccessCase::Getter: {
236                     callLinkStatus = std::make_unique<CallLinkStatus>();
237                     if (CallLinkInfo* callLinkInfo = access.as<GetterSetterAccessCase>().callLinkInfo()) {
238                         *callLinkStatus = CallLinkStatus::computeFor(
239                             locker, profiledBlock, *callLinkInfo, callExitSiteData);
240                     }
241                     break;
242                 }
243                 case AccessCase::CustomAccessorGetter: {
244                     customAccessorGetter = access.as<GetterSetterAccessCase>().customAccessor();
245                     domAttribute = access.as<GetterSetterAccessCase>().domAttribute();
246                     if (!domAttribute)
247                         return GetByIdStatus(JSC::slowVersion(summary));
248                     result.m_state = Custom;
249                     break;
250                 }
251                 default: {
252                     // FIXME: It would be totally sweet to support more of these at some point in the
253                     // future. https://bugs.webkit.org/show_bug.cgi?id=133052
254                     return GetByIdStatus(JSC::slowVersion(summary));
255                 } }
256
257                 ASSERT((AccessCase::Miss == access.type()) == (access.offset() == invalidOffset));
258                 GetByIdVariant variant(
259                     StructureSet(structure), complexGetStatus.offset(),
260                     complexGetStatus.conditionSet(), WTFMove(callLinkStatus),
261                     intrinsicFunction,
262                     customAccessorGetter,
263                     domAttribute);
264
265                 if (!result.appendVariant(variant))
266                     return GetByIdStatus(JSC::slowVersion(summary));
267
268                 if (domAttribute) {
269                     // Give up when cutom accesses are not merged into one.
270                     if (result.numVariants() != 1)
271                         return GetByIdStatus(JSC::slowVersion(summary));
272                 } else {
273                     // Give up when custom access and simple access are mixed.
274                     if (result.m_state == Custom)
275                         return GetByIdStatus(JSC::slowVersion(summary));
276                 }
277                 break;
278             } }
279         }
280         
281         return result;
282     }
283         
284     default:
285         return GetByIdStatus(JSC::slowVersion(summary));
286     }
287     
288     RELEASE_ASSERT_NOT_REACHED();
289     return GetByIdStatus();
290 }
291 #endif // ENABLE(JIT)
292
293 GetByIdStatus GetByIdStatus::computeFor(
294     CodeBlock* profiledBlock, ICStatusMap& baselineMap,
295     ICStatusContextStack& icContextStack, CodeOrigin codeOrigin, UniquedStringImpl* uid)
296 {
297     CallLinkStatus::ExitSiteData callExitSiteData =
298         CallLinkStatus::computeExitSiteData(profiledBlock, codeOrigin.bytecodeIndex);
299     ExitFlag didExit = hasBadCacheExitSite(profiledBlock, codeOrigin.bytecodeIndex);
300     
301     for (ICStatusContext* context : icContextStack) {
302         ICStatus status = context->get(codeOrigin);
303         
304         auto bless = [&] (const GetByIdStatus& result) -> GetByIdStatus {
305             if (!context->isInlined(codeOrigin)) {
306                 // Merge with baseline result, which also happens to contain exit data for both
307                 // inlined and not-inlined.
308                 GetByIdStatus baselineResult = computeFor(
309                     profiledBlock, baselineMap, codeOrigin.bytecodeIndex, uid, didExit,
310                     callExitSiteData);
311                 baselineResult.merge(result);
312                 return baselineResult;
313             }
314             if (didExit.isSet(ExitFromInlined))
315                 return result.slowVersion();
316             return result;
317         };
318         
319         if (status.stubInfo) {
320             GetByIdStatus result;
321             {
322                 ConcurrentJSLocker locker(context->optimizedCodeBlock->m_lock);
323                 result = computeForStubInfoWithoutExitSiteFeedback(
324                     locker, context->optimizedCodeBlock, status.stubInfo, uid, callExitSiteData);
325             }
326             if (result.isSet())
327                 return bless(result);
328         }
329         
330         if (status.getStatus)
331             return bless(*status.getStatus);
332     }
333     
334     return computeFor(profiledBlock, baselineMap, codeOrigin.bytecodeIndex, uid, didExit, callExitSiteData);
335 }
336
337 GetByIdStatus GetByIdStatus::computeFor(const StructureSet& set, UniquedStringImpl* uid)
338 {
339     // For now we only handle the super simple self access case. We could handle the
340     // prototype case in the future.
341     //
342     // Note that this code is also used for GetByIdDirect since this function only looks
343     // into direct properties. When supporting prototype chains, we should split this for
344     // GetById and GetByIdDirect.
345     
346     if (set.isEmpty())
347         return GetByIdStatus();
348
349     if (parseIndex(*uid))
350         return GetByIdStatus(TakesSlowPath);
351     
352     GetByIdStatus result;
353     result.m_state = Simple;
354     result.m_wasSeenInJIT = false;
355     for (unsigned i = 0; i < set.size(); ++i) {
356         Structure* structure = set[i];
357         if (structure->typeInfo().overridesGetOwnPropertySlot() && structure->typeInfo().type() != GlobalObjectType)
358             return GetByIdStatus(TakesSlowPath);
359         
360         if (!structure->propertyAccessesAreCacheable())
361             return GetByIdStatus(TakesSlowPath);
362         
363         unsigned attributes;
364         PropertyOffset offset = structure->getConcurrently(uid, attributes);
365         if (!isValidOffset(offset))
366             return GetByIdStatus(TakesSlowPath); // It's probably a prototype lookup. Give up on life for now, even though we could totally be way smarter about it.
367         if (attributes & PropertyAttribute::Accessor)
368             return GetByIdStatus(MakesCalls); // We could be smarter here, like strength-reducing this to a Call.
369         if (attributes & PropertyAttribute::CustomAccessor)
370             return GetByIdStatus(TakesSlowPath);
371         
372         if (!result.appendVariant(GetByIdVariant(structure, offset)))
373             return GetByIdStatus(TakesSlowPath);
374     }
375     
376     return result;
377 }
378
379 bool GetByIdStatus::makesCalls() const
380 {
381     switch (m_state) {
382     case NoInformation:
383     case TakesSlowPath:
384     case Custom:
385     case ModuleNamespace:
386         return false;
387     case Simple:
388         for (unsigned i = m_variants.size(); i--;) {
389             if (m_variants[i].callLinkStatus())
390                 return true;
391         }
392         return false;
393     case MakesCalls:
394         return true;
395     }
396     RELEASE_ASSERT_NOT_REACHED();
397
398     return false;
399 }
400
401 GetByIdStatus GetByIdStatus::slowVersion() const
402 {
403     return GetByIdStatus(makesCalls() ? MakesCalls : TakesSlowPath, wasSeenInJIT());
404 }
405
406 void GetByIdStatus::merge(const GetByIdStatus& other)
407 {
408     if (other.m_state == NoInformation)
409         return;
410     
411     auto mergeSlow = [&] () {
412         *this = GetByIdStatus((makesCalls() || other.makesCalls()) ? MakesCalls : TakesSlowPath);
413     };
414     
415     switch (m_state) {
416     case NoInformation:
417         *this = other;
418         return;
419         
420     case Simple:
421     case Custom:
422         if (m_state != other.m_state)
423             return mergeSlow();
424         
425         for (const GetByIdVariant& otherVariant : other.m_variants) {
426             if (!appendVariant(otherVariant))
427                 return mergeSlow();
428         }
429         return;
430         
431     case ModuleNamespace:
432         if (other.m_state != ModuleNamespace)
433             return mergeSlow();
434         
435         if (m_moduleNamespaceObject != other.m_moduleNamespaceObject)
436             return mergeSlow();
437         
438         if (m_moduleEnvironment != other.m_moduleEnvironment)
439             return mergeSlow();
440         
441         if (m_scopeOffset != other.m_scopeOffset)
442             return mergeSlow();
443         
444         return;
445         
446     case TakesSlowPath:
447     case MakesCalls:
448         return mergeSlow();
449     }
450     
451     RELEASE_ASSERT_NOT_REACHED();
452 }
453
454 void GetByIdStatus::filter(const StructureSet& set)
455 {
456     if (m_state != Simple)
457         return;
458     filterICStatusVariants(m_variants, set);
459     if (m_variants.isEmpty())
460         m_state = NoInformation;
461 }
462
463 void GetByIdStatus::markIfCheap(SlotVisitor& visitor)
464 {
465     for (GetByIdVariant& variant : m_variants)
466         variant.markIfCheap(visitor);
467 }
468
469 bool GetByIdStatus::finalize()
470 {
471     for (GetByIdVariant& variant : m_variants) {
472         if (!variant.finalize())
473             return false;
474     }
475     if (m_moduleNamespaceObject && !Heap::isMarked(m_moduleNamespaceObject))
476         return false;
477     if (m_moduleEnvironment && !Heap::isMarked(m_moduleEnvironment))
478         return false;
479     return true;
480 }
481
482 void GetByIdStatus::dump(PrintStream& out) const
483 {
484     out.print("(");
485     switch (m_state) {
486     case NoInformation:
487         out.print("NoInformation");
488         break;
489     case Simple:
490         out.print("Simple");
491         break;
492     case Custom:
493         out.print("Custom");
494         break;
495     case ModuleNamespace:
496         out.print("ModuleNamespace");
497         break;
498     case TakesSlowPath:
499         out.print("TakesSlowPath");
500         break;
501     case MakesCalls:
502         out.print("MakesCalls");
503         break;
504     }
505     out.print(", ", listDump(m_variants), ", seenInJIT = ", m_wasSeenInJIT, ")");
506 }
507
508 } // namespace JSC
509