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