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