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