/ bytecode / CallLinkStatus.cpp
CallLinkStatus.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 "CallLinkStatus.h"
 28  
 29  #include "BytecodeStructs.h"
 30  #include "CallLinkInfo.h"
 31  #include "CodeBlock.h"
 32  #include "JSCInlines.h"
 33  #include "LLIntCallLinkInfo.h"
 34  #include <wtf/CommaPrinter.h>
 35  #include <wtf/ListDump.h>
 36  
 37  namespace JSC {
 38  
 39  namespace CallLinkStatusInternal {
 40  static constexpr bool verbose = false;
 41  }
 42  
 43  CallLinkStatus::CallLinkStatus(JSValue value)
 44      : m_couldTakeSlowPath(false)
 45      , m_isProved(false)
 46  {
 47      if (!value || !value.isCell()) {
 48          m_couldTakeSlowPath = true;
 49          return;
 50      }
 51      
 52      m_variants.append(CallVariant(value.asCell()));
 53  }
 54  
 55  CallLinkStatus CallLinkStatus::computeFromLLInt(const ConcurrentJSLocker&, CodeBlock* profiledBlock, BytecodeIndex bytecodeIndex)
 56  {
 57      UNUSED_PARAM(profiledBlock);
 58      UNUSED_PARAM(bytecodeIndex);
 59  #if ENABLE(DFG_JIT)
 60      if (profiledBlock->unlinkedCodeBlock()->hasExitSite(DFG::FrequentExitSite(bytecodeIndex, BadConstantValue))) {
 61          // We could force this to be a closure call, but instead we'll just assume that it
 62          // takes slow path.
 63          return takesSlowPath();
 64      }
 65  #endif
 66  
 67      auto instruction = profiledBlock->instructions().at(bytecodeIndex.offset());
 68      OpcodeID op = instruction->opcodeID();
 69  
 70      LLIntCallLinkInfo* callLinkInfo;
 71      switch (op) {
 72      case op_call:
 73          callLinkInfo = &instruction->as<OpCall>().metadata(profiledBlock).m_callLinkInfo;
 74          break;
 75      case op_construct:
 76          callLinkInfo = &instruction->as<OpConstruct>().metadata(profiledBlock).m_callLinkInfo;
 77          break;
 78      case op_tail_call:
 79          callLinkInfo = &instruction->as<OpTailCall>().metadata(profiledBlock).m_callLinkInfo;
 80          break;
 81      case op_iterator_open:
 82          callLinkInfo = &instruction->as<OpIteratorOpen>().metadata(profiledBlock).m_callLinkInfo;
 83          break;
 84      case op_iterator_next:
 85          callLinkInfo = &instruction->as<OpIteratorNext>().metadata(profiledBlock).m_callLinkInfo;
 86          break;
 87  
 88      default:
 89          return CallLinkStatus();
 90      }
 91      
 92      
 93      return CallLinkStatus(callLinkInfo->lastSeenCallee());
 94  }
 95  
 96  CallLinkStatus CallLinkStatus::computeFor(
 97      CodeBlock* profiledBlock, BytecodeIndex bytecodeIndex, const ICStatusMap& map,
 98      ExitSiteData exitSiteData)
 99  {
100      ConcurrentJSLocker locker(profiledBlock->m_lock);
101      
102      UNUSED_PARAM(profiledBlock);
103      UNUSED_PARAM(bytecodeIndex);
104      UNUSED_PARAM(map);
105      UNUSED_PARAM(exitSiteData);
106  #if ENABLE(DFG_JIT)
107      CallLinkInfo* callLinkInfo = map.get(CodeOrigin(bytecodeIndex)).callLinkInfo;
108      if (!callLinkInfo) {
109          if (exitSiteData.takesSlowPath)
110              return takesSlowPath();
111          return computeFromLLInt(locker, profiledBlock, bytecodeIndex);
112      }
113      
114      return computeFor(locker, profiledBlock, *callLinkInfo, exitSiteData);
115  #else
116      return CallLinkStatus();
117  #endif
118  }
119  
120  CallLinkStatus CallLinkStatus::computeFor(
121      CodeBlock* profiledBlock, BytecodeIndex bytecodeIndex, const ICStatusMap& map)
122  {
123      return computeFor(profiledBlock, bytecodeIndex, map, computeExitSiteData(profiledBlock, bytecodeIndex));
124  }
125  
126  CallLinkStatus::ExitSiteData CallLinkStatus::computeExitSiteData(CodeBlock* profiledBlock, BytecodeIndex bytecodeIndex)
127  {
128      ExitSiteData exitSiteData;
129  #if ENABLE(DFG_JIT)
130      UnlinkedCodeBlock* codeBlock = profiledBlock->unlinkedCodeBlock();
131      ConcurrentJSLocker locker(codeBlock->m_lock);
132  
133      auto takesSlowPath = [&] (ExitingInlineKind inlineKind) -> ExitFlag {
134          return ExitFlag(
135              codeBlock->hasExitSite(locker, DFG::FrequentExitSite(bytecodeIndex, BadType, ExitFromAnything, inlineKind))
136              || codeBlock->hasExitSite(locker, DFG::FrequentExitSite(bytecodeIndex, BadExecutable, ExitFromAnything, inlineKind)),
137              inlineKind);
138      };
139      
140      auto badFunction = [&] (ExitingInlineKind inlineKind) -> ExitFlag {
141          return ExitFlag(codeBlock->hasExitSite(locker, DFG::FrequentExitSite(bytecodeIndex, BadConstantValue, ExitFromAnything, inlineKind)), inlineKind);
142      };
143      
144      exitSiteData.takesSlowPath |= takesSlowPath(ExitFromNotInlined);
145      exitSiteData.takesSlowPath |= takesSlowPath(ExitFromInlined);
146      exitSiteData.badFunction |= badFunction(ExitFromNotInlined);
147      exitSiteData.badFunction |= badFunction(ExitFromInlined);
148  #else
149      UNUSED_PARAM(profiledBlock);
150      UNUSED_PARAM(bytecodeIndex);
151  #endif
152      
153      return exitSiteData;
154  }
155  
156  #if ENABLE(JIT)
157  CallLinkStatus CallLinkStatus::computeFor(
158      const ConcurrentJSLocker& locker, CodeBlock* profiledBlock, CallLinkInfo& callLinkInfo)
159  {
160      // We don't really need this, but anytime we have to debug this code, it becomes indispensable.
161      UNUSED_PARAM(profiledBlock);
162      
163      CallLinkStatus result = computeFromCallLinkInfo(locker, callLinkInfo);
164      result.m_maxArgumentCountIncludingThis = callLinkInfo.maxArgumentCountIncludingThis();
165      return result;
166  }
167  
168  CallLinkStatus CallLinkStatus::computeFromCallLinkInfo(
169      const ConcurrentJSLocker&, CallLinkInfo& callLinkInfo)
170  {
171      // We cannot tell you anything about direct calls.
172      if (callLinkInfo.isDirect())
173          return CallLinkStatus();
174      
175      if (callLinkInfo.clearedByGC() || callLinkInfo.clearedByVirtual())
176          return takesSlowPath();
177      
178      // Note that despite requiring that the locker is held, this code is racy with respect
179      // to the CallLinkInfo: it may get cleared while this code runs! This is because
180      // CallLinkInfo::unlink() may be called from a different CodeBlock than the one that owns
181      // the CallLinkInfo and currently we save space by not having CallLinkInfos know who owns
182      // them. So, there is no way for either the caller of CallLinkInfo::unlock() or unlock()
183      // itself to figure out which lock to lock.
184      //
185      // Fortunately, that doesn't matter. The only things we ask of CallLinkInfo - the slow
186      // path count, the stub, and the target - can all be asked racily. Stubs and targets can
187      // only be deleted at next GC, so if we load a non-null one, then it must contain data
188      // that is still marginally valid (i.e. the pointers ain't stale). This kind of raciness
189      // is probably OK for now.
190      
191      // PolymorphicCallStubRoutine is a GCAwareJITStubRoutine, so if non-null, it will stay alive
192      // until next GC even if the CallLinkInfo is concurrently cleared. Also, the variants list is
193      // never mutated after the PolymorphicCallStubRoutine is instantiated. We have some conservative
194      // fencing in place to make sure that we see the variants list after construction.
195      if (PolymorphicCallStubRoutine* stub = callLinkInfo.stub()) {
196          WTF::loadLoadFence();
197          
198          if (!stub->hasEdges()) {
199              // This means we have an FTL profile, which has incomplete information.
200              //
201              // It's not clear if takesSlowPath() or CallLinkStatus() are appropriate here, but I
202              // think that takesSlowPath() is a narrow winner.
203              //
204              // Either way, this is telling us that the FTL had been led to believe that it's
205              // profitable not to inline anything.
206              return takesSlowPath();
207          }
208          
209          CallEdgeList edges = stub->edges();
210          
211          // Now that we've loaded the edges list, there are no further concurrency concerns. We will
212          // just manipulate and prune this list to our liking - mostly removing entries that are too
213          // infrequent and ensuring that it's sorted in descending order of frequency.
214          
215          RELEASE_ASSERT(edges.size());
216          
217          std::sort(
218              edges.begin(), edges.end(),
219              [] (CallEdge a, CallEdge b) {
220                  return a.count() > b.count();
221              });
222          RELEASE_ASSERT(edges.first().count() >= edges.last().count());
223          
224          double totalCallsToKnown = 0;
225          double totalCallsToUnknown = callLinkInfo.slowPathCount();
226          CallVariantList variants;
227          for (size_t i = 0; i < edges.size(); ++i) {
228              CallEdge edge = edges[i];
229              // If the call is at the tail of the distribution, then we don't optimize it and we
230              // treat it as if it was a call to something unknown. We define the tail as being either
231              // a call that doesn't belong to the N most frequent callees (N =
232              // maxPolymorphicCallVariantsForInlining) or that has a total call count that is too
233              // small.
234              if (i >= Options::maxPolymorphicCallVariantsForInlining()
235                  || edge.count() < Options::frequentCallThreshold())
236                  totalCallsToUnknown += edge.count();
237              else {
238                  totalCallsToKnown += edge.count();
239                  variants.append(edge.callee());
240              }
241          }
242          
243          // Bail if we didn't find any calls that qualified.
244          RELEASE_ASSERT(!!totalCallsToKnown == !!variants.size());
245          if (variants.isEmpty())
246              return takesSlowPath();
247          
248          // We require that the distribution of callees is skewed towards a handful of common ones.
249          if (totalCallsToKnown / totalCallsToUnknown < Options::minimumCallToKnownRate())
250              return takesSlowPath();
251          
252          RELEASE_ASSERT(totalCallsToKnown);
253          RELEASE_ASSERT(variants.size());
254          
255          CallLinkStatus result;
256          result.m_variants = variants;
257          result.m_couldTakeSlowPath = !!totalCallsToUnknown;
258          result.m_isBasedOnStub = true;
259          return result;
260      }
261      
262      CallLinkStatus result;
263      
264      if (JSObject* target = callLinkInfo.lastSeenCallee()) {
265          CallVariant variant(target);
266          if (callLinkInfo.hasSeenClosure())
267              variant = variant.despecifiedClosure();
268          result.m_variants.append(variant);
269      }
270      
271      result.m_couldTakeSlowPath = !!callLinkInfo.slowPathCount();
272  
273      return result;
274  }
275  
276  CallLinkStatus CallLinkStatus::computeFor(
277      const ConcurrentJSLocker& locker, CodeBlock* profiledBlock, CallLinkInfo& callLinkInfo,
278      ExitSiteData exitSiteData, ExitingInlineKind inlineKind)
279  {
280      CallLinkStatus result = computeFor(locker, profiledBlock, callLinkInfo);
281      result.accountForExits(exitSiteData, inlineKind);
282      return result;
283  }
284  
285  void CallLinkStatus::accountForExits(ExitSiteData exitSiteData, ExitingInlineKind inlineKind)
286  {
287      if (exitSiteData.badFunction.isSet(inlineKind)) {
288          if (isBasedOnStub()) {
289              // If we have a polymorphic stub, then having an exit site is not quite so useful. In
290              // most cases, the information in the stub has higher fidelity.
291              makeClosureCall();
292          } else {
293              // We might not have a polymorphic stub for any number of reasons. When this happens, we
294              // are in less certain territory, so exit sites mean a lot.
295              m_couldTakeSlowPath = true;
296          }
297      }
298      
299      if (exitSiteData.takesSlowPath.isSet(inlineKind))
300          m_couldTakeSlowPath = true;
301  }
302  
303  CallLinkStatus CallLinkStatus::computeFor(
304      CodeBlock* profiledBlock, CodeOrigin codeOrigin,
305      const ICStatusMap& baselineMap, const ICStatusContextStack& optimizedStack)
306  {
307      if (CallLinkStatusInternal::verbose)
308          dataLog("Figuring out call profiling for ", codeOrigin, "\n");
309      ExitSiteData exitSiteData = computeExitSiteData(profiledBlock, codeOrigin.bytecodeIndex());
310      if (CallLinkStatusInternal::verbose) {
311          dataLog("takesSlowPath = ", exitSiteData.takesSlowPath, "\n");
312          dataLog("badFunction = ", exitSiteData.badFunction, "\n");
313      }
314      
315      for (const ICStatusContext* context : optimizedStack) {
316          if (CallLinkStatusInternal::verbose)
317              dataLog("Examining status in ", pointerDump(context->optimizedCodeBlock), "\n");
318          ICStatus status = context->get(codeOrigin);
319          
320          // If we have both a CallLinkStatus and a callLinkInfo for the same origin, then it means
321          // one of two things:
322          //
323          // 1) We initially thought that we'd be able to inline this call so we recorded a status
324          //    but then we realized that it was pointless and gave up and emitted a normal call
325          //    anyway.
326          //
327          // 2) We did a polymorphic call inline that had a slow path case.
328          //
329          // In the first case, it's essential that we use the callLinkInfo, since the status may
330          // be polymorphic but the link info benefits from polyvariant profiling.
331          //
332          // In the second case, it's essential that we use the status, since the callLinkInfo
333          // corresponds to the slow case.
334          //
335          // It would be annoying to distinguish those two cases. However, we know that:
336          //
337          // If the first case happens in the FTL, then it means that even with polyvariant
338          // profiling, we failed to do anything useful. That suggests that in the FTL, it's OK to
339          // prioritize the status. That status will again tell us to not do anything useful.
340          //
341          // The second case only happens in the FTL.
342          //
343          // Therefore, we prefer the status in the FTL and the info in the DFG.
344          //
345          // Luckily, this case doesn't matter for the other ICStatuses, since they never do the
346          // fast-path-slow-path control-flow-diamond style of IC inlining. It's either all fast
347          // path or it's a full IC. So, for them, if there is an IC status then it means case (1).
348          
349          bool checkStatusFirst = context->optimizedCodeBlock->jitType() == JITType::FTLJIT;
350          
351          auto bless = [&] (CallLinkStatus& result) {
352              if (!context->isInlined(codeOrigin))
353                  result.merge(computeFor(profiledBlock, codeOrigin.bytecodeIndex(), baselineMap, exitSiteData));
354          };
355          
356          auto checkInfo = [&] () -> CallLinkStatus {
357              if (!status.callLinkInfo)
358                  return CallLinkStatus();
359              
360              if (CallLinkStatusInternal::verbose)
361                  dataLog("Have CallLinkInfo with CodeOrigin = ", status.callLinkInfo->codeOrigin(), "\n");
362              CallLinkStatus result;
363              {
364                  ConcurrentJSLocker locker(context->optimizedCodeBlock->m_lock);
365                  result = computeFor(
366                      locker, context->optimizedCodeBlock, *status.callLinkInfo, exitSiteData,
367                      context->inlineKind(codeOrigin));
368                  if (CallLinkStatusInternal::verbose)
369                      dataLog("Got result: ", result, "\n");
370              }
371              bless(result);
372              return result;
373          };
374          
375          auto checkStatus = [&] () -> CallLinkStatus {
376              if (!status.callStatus)
377                  return CallLinkStatus();
378              CallLinkStatus result = *status.callStatus;
379              if (CallLinkStatusInternal::verbose)
380                  dataLog("Have callStatus: ", result, "\n");
381              result.accountForExits(exitSiteData, context->inlineKind(codeOrigin));
382              bless(result);
383              return result;
384          };
385          
386          if (checkStatusFirst) {
387              if (CallLinkStatus result = checkStatus())
388                  return result;
389              if (CallLinkStatus result = checkInfo())
390                  return result;
391              continue;
392          }
393          
394          if (CallLinkStatus result = checkInfo())
395              return result;
396          if (CallLinkStatus result = checkStatus())
397              return result;
398      }
399      
400      return computeFor(profiledBlock, codeOrigin.bytecodeIndex(), baselineMap, exitSiteData);
401  }
402  #endif
403  
404  void CallLinkStatus::setProvenConstantCallee(CallVariant variant)
405  {
406      m_variants = CallVariantList{ variant };
407      m_couldTakeSlowPath = false;
408      m_isProved = true;
409  }
410  
411  bool CallLinkStatus::isClosureCall() const
412  {
413      for (unsigned i = m_variants.size(); i--;) {
414          if (m_variants[i].isClosureCall())
415              return true;
416      }
417      return false;
418  }
419  
420  void CallLinkStatus::makeClosureCall()
421  {
422      m_variants = despecifiedVariantList(m_variants);
423  }
424  
425  bool CallLinkStatus::finalize(VM& vm)
426  {
427      for (CallVariant& variant : m_variants) {
428          if (!variant.finalize(vm))
429              return false;
430      }
431      return true;
432  }
433  
434  void CallLinkStatus::merge(const CallLinkStatus& other)
435  {
436      m_couldTakeSlowPath |= other.m_couldTakeSlowPath;
437      
438      for (const CallVariant& otherVariant : other.m_variants) {
439          bool found = false;
440          for (CallVariant& thisVariant : m_variants) {
441              if (thisVariant.merge(otherVariant)) {
442                  found = true;
443                  break;
444              }
445          }
446          if (!found)
447              m_variants.append(otherVariant);
448      }
449  }
450  
451  void CallLinkStatus::filter(VM& vm, JSValue value)
452  {
453      m_variants.removeAllMatching(
454          [&] (CallVariant& variant) -> bool {
455              variant.filter(vm, value);
456              return !variant;
457          });
458  }
459  
460  void CallLinkStatus::dump(PrintStream& out) const
461  {
462      if (!isSet()) {
463          out.print("Not Set");
464          return;
465      }
466      
467      CommaPrinter comma;
468      
469      if (m_isProved)
470          out.print(comma, "Statically Proved");
471      
472      if (m_couldTakeSlowPath)
473          out.print(comma, "Could Take Slow Path");
474      
475      if (m_isBasedOnStub)
476          out.print(comma, "Based On Stub");
477      
478      if (!m_variants.isEmpty())
479          out.print(comma, listDump(m_variants));
480      
481      if (m_maxArgumentCountIncludingThis)
482          out.print(comma, "maxArgumentCountIncludingThis = ", m_maxArgumentCountIncludingThis);
483  }
484  
485  } // namespace JSC
486