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