| 1 | /* |
| 2 | * Copyright (C) 2017-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 "AccessCase.h" |
| 28 | |
| 29 | #if ENABLE(JIT) |
| 30 | |
| 31 | #include "CCallHelpers.h" |
| 32 | #include "CallLinkInfo.h" |
| 33 | #include "DOMJITGetterSetter.h" |
| 34 | #include "DirectArguments.h" |
| 35 | #include "GetterSetter.h" |
| 36 | #include "GetterSetterAccessCase.h" |
| 37 | #include "InstanceOfAccessCase.h" |
| 38 | #include "IntrinsicGetterAccessCase.h" |
| 39 | #include "JSCInlines.h" |
| 40 | #include "JSModuleEnvironment.h" |
| 41 | #include "JSModuleNamespaceObject.h" |
| 42 | #include "LinkBuffer.h" |
| 43 | #include "ModuleNamespaceAccessCase.h" |
| 44 | #include "PolymorphicAccess.h" |
| 45 | #include "ScopedArguments.h" |
| 46 | #include "ScratchRegisterAllocator.h" |
| 47 | #include "StructureStubInfo.h" |
| 48 | #include "SuperSampler.h" |
| 49 | #include "ThunkGenerators.h" |
| 50 | |
| 51 | namespace JSC { |
| 52 | |
| 53 | namespace AccessCaseInternal { |
| 54 | static const bool verbose = false; |
| 55 | } |
| 56 | |
| 57 | AccessCase::AccessCase(VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain) |
| 58 | : m_type(type) |
| 59 | , m_offset(offset) |
| 60 | , m_polyProtoAccessChain(WTFMove(prototypeAccessChain)) |
| 61 | { |
| 62 | m_structure.setMayBeNull(vm, owner, structure); |
| 63 | m_conditionSet = conditionSet; |
| 64 | } |
| 65 | |
| 66 | std::unique_ptr<AccessCase> AccessCase::create(VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain) |
| 67 | { |
| 68 | switch (type) { |
| 69 | case InHit: |
| 70 | case InMiss: |
| 71 | break; |
| 72 | case ArrayLength: |
| 73 | case StringLength: |
| 74 | case DirectArgumentsLength: |
| 75 | case ScopedArgumentsLength: |
| 76 | case ModuleNamespaceLoad: |
| 77 | case Replace: |
| 78 | case InstanceOfGeneric: |
| 79 | RELEASE_ASSERT(!prototypeAccessChain); |
| 80 | break; |
| 81 | default: |
| 82 | RELEASE_ASSERT_NOT_REACHED(); |
| 83 | }; |
| 84 | |
| 85 | return std::unique_ptr<AccessCase>(new AccessCase(vm, owner, type, offset, structure, conditionSet, WTFMove(prototypeAccessChain))); |
| 86 | } |
| 87 | |
| 88 | std::unique_ptr<AccessCase> AccessCase::create( |
| 89 | VM& vm, JSCell* owner, PropertyOffset offset, Structure* oldStructure, Structure* newStructure, |
| 90 | const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain) |
| 91 | { |
| 92 | RELEASE_ASSERT(oldStructure == newStructure->previousID()); |
| 93 | |
| 94 | // Skip optimizing the case where we need a realloc, if we don't have |
| 95 | // enough registers to make it happen. |
| 96 | if (GPRInfo::numberOfRegisters < 6 |
| 97 | && oldStructure->outOfLineCapacity() != newStructure->outOfLineCapacity() |
| 98 | && oldStructure->outOfLineCapacity()) { |
| 99 | return nullptr; |
| 100 | } |
| 101 | |
| 102 | return std::unique_ptr<AccessCase>(new AccessCase(vm, owner, Transition, offset, newStructure, conditionSet, WTFMove(prototypeAccessChain))); |
| 103 | } |
| 104 | |
| 105 | AccessCase::~AccessCase() |
| 106 | { |
| 107 | } |
| 108 | |
| 109 | std::unique_ptr<AccessCase> AccessCase::fromStructureStubInfo( |
| 110 | VM& vm, JSCell* owner, StructureStubInfo& stubInfo) |
| 111 | { |
| 112 | switch (stubInfo.cacheType) { |
| 113 | case CacheType::GetByIdSelf: |
| 114 | return ProxyableAccessCase::create(vm, owner, Load, stubInfo.u.byIdSelf.offset, stubInfo.u.byIdSelf.baseObjectStructure.get()); |
| 115 | |
| 116 | case CacheType::PutByIdReplace: |
| 117 | return AccessCase::create(vm, owner, Replace, stubInfo.u.byIdSelf.offset, stubInfo.u.byIdSelf.baseObjectStructure.get()); |
| 118 | |
| 119 | case CacheType::InByIdSelf: |
| 120 | return AccessCase::create(vm, owner, InHit, stubInfo.u.byIdSelf.offset, stubInfo.u.byIdSelf.baseObjectStructure.get()); |
| 121 | |
| 122 | case CacheType::ArrayLength: |
| 123 | return AccessCase::create(vm, owner, AccessCase::ArrayLength); |
| 124 | |
| 125 | case CacheType::StringLength: |
| 126 | return AccessCase::create(vm, owner, AccessCase::StringLength); |
| 127 | |
| 128 | default: |
| 129 | return nullptr; |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | bool AccessCase::hasAlternateBase() const |
| 134 | { |
| 135 | return !conditionSet().isEmpty(); |
| 136 | } |
| 137 | |
| 138 | JSObject* AccessCase::alternateBase() const |
| 139 | { |
| 140 | return conditionSet().slotBaseCondition().object(); |
| 141 | } |
| 142 | |
| 143 | std::unique_ptr<AccessCase> AccessCase::clone() const |
| 144 | { |
| 145 | std::unique_ptr<AccessCase> result(new AccessCase(*this)); |
| 146 | result->resetState(); |
| 147 | return result; |
| 148 | } |
| 149 | |
| 150 | Vector<WatchpointSet*, 2> AccessCase::commit(VM& vm, const Identifier& ident) |
| 151 | { |
| 152 | // It's fine to commit something that is already committed. That arises when we switch to using |
| 153 | // newly allocated watchpoints. When it happens, it's not efficient - but we think that's OK |
| 154 | // because most AccessCases have no extra watchpoints anyway. |
| 155 | RELEASE_ASSERT(m_state == Primordial || m_state == Committed); |
| 156 | |
| 157 | Vector<WatchpointSet*, 2> result; |
| 158 | Structure* structure = this->structure(); |
| 159 | |
| 160 | if (!ident.isNull()) { |
| 161 | if ((structure && structure->needImpurePropertyWatchpoint()) |
| 162 | || m_conditionSet.needImpurePropertyWatchpoint() |
| 163 | || (m_polyProtoAccessChain && m_polyProtoAccessChain->needImpurePropertyWatchpoint())) |
| 164 | result.append(vm.ensureWatchpointSetForImpureProperty(ident)); |
| 165 | } |
| 166 | |
| 167 | if (additionalSet()) |
| 168 | result.append(additionalSet()); |
| 169 | |
| 170 | if (structure |
| 171 | && structure->hasRareData() |
| 172 | && structure->rareData()->hasSharedPolyProtoWatchpoint() |
| 173 | && structure->rareData()->sharedPolyProtoWatchpoint()->isStillValid()) { |
| 174 | WatchpointSet* set = structure->rareData()->sharedPolyProtoWatchpoint()->inflate(); |
| 175 | result.append(set); |
| 176 | } |
| 177 | |
| 178 | m_state = Committed; |
| 179 | |
| 180 | return result; |
| 181 | } |
| 182 | |
| 183 | bool AccessCase::guardedByStructureCheck() const |
| 184 | { |
| 185 | if (viaProxy()) |
| 186 | return false; |
| 187 | |
| 188 | if (m_polyProtoAccessChain) |
| 189 | return false; |
| 190 | |
| 191 | switch (m_type) { |
| 192 | case ArrayLength: |
| 193 | case StringLength: |
| 194 | case DirectArgumentsLength: |
| 195 | case ScopedArgumentsLength: |
| 196 | case ModuleNamespaceLoad: |
| 197 | case InstanceOfHit: |
| 198 | case InstanceOfMiss: |
| 199 | case InstanceOfGeneric: |
| 200 | return false; |
| 201 | default: |
| 202 | return true; |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | bool AccessCase::doesCalls(Vector<JSCell*>* cellsToMark) const |
| 207 | { |
| 208 | switch (type()) { |
| 209 | case Getter: |
| 210 | case Setter: |
| 211 | case CustomValueGetter: |
| 212 | case CustomAccessorGetter: |
| 213 | case CustomValueSetter: |
| 214 | case CustomAccessorSetter: |
| 215 | return true; |
| 216 | case Transition: |
| 217 | if (newStructure()->outOfLineCapacity() != structure()->outOfLineCapacity() |
| 218 | && structure()->couldHaveIndexingHeader()) { |
| 219 | if (cellsToMark) |
| 220 | cellsToMark->append(newStructure()); |
| 221 | return true; |
| 222 | } |
| 223 | return false; |
| 224 | default: |
| 225 | return false; |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | bool AccessCase::couldStillSucceed() const |
| 230 | { |
| 231 | return m_conditionSet.structuresEnsureValidityAssumingImpurePropertyWatchpoint(); |
| 232 | } |
| 233 | |
| 234 | bool AccessCase::canReplace(const AccessCase& other) const |
| 235 | { |
| 236 | // This puts in a good effort to try to figure out if 'other' is made superfluous by '*this'. |
| 237 | // It's fine for this to return false if it's in doubt. |
| 238 | // |
| 239 | // Note that if A->guardedByStructureCheck() && B->guardedByStructureCheck() then |
| 240 | // A->canReplace(B) == B->canReplace(A). |
| 241 | |
| 242 | switch (type()) { |
| 243 | case ArrayLength: |
| 244 | case StringLength: |
| 245 | case DirectArgumentsLength: |
| 246 | case ScopedArgumentsLength: |
| 247 | return other.type() == type(); |
| 248 | case ModuleNamespaceLoad: { |
| 249 | if (other.type() != type()) |
| 250 | return false; |
| 251 | auto& thisCase = this->as<ModuleNamespaceAccessCase>(); |
| 252 | auto& otherCase = this->as<ModuleNamespaceAccessCase>(); |
| 253 | return thisCase.moduleNamespaceObject() == otherCase.moduleNamespaceObject(); |
| 254 | } |
| 255 | case InstanceOfHit: |
| 256 | case InstanceOfMiss: { |
| 257 | if (other.type() != type()) |
| 258 | return false; |
| 259 | |
| 260 | if (this->as<InstanceOfAccessCase>().prototype() != other.as<InstanceOfAccessCase>().prototype()) |
| 261 | return false; |
| 262 | |
| 263 | return structure() == other.structure(); |
| 264 | } |
| 265 | case InstanceOfGeneric: |
| 266 | switch (other.type()) { |
| 267 | case InstanceOfGeneric: |
| 268 | case InstanceOfHit: |
| 269 | case InstanceOfMiss: |
| 270 | return true; |
| 271 | default: |
| 272 | return false; |
| 273 | } |
| 274 | default: |
| 275 | if (other.type() != type()) |
| 276 | return false; |
| 277 | |
| 278 | if (m_polyProtoAccessChain) { |
| 279 | if (!other.m_polyProtoAccessChain) |
| 280 | return false; |
| 281 | // This is the only check we need since PolyProtoAccessChain contains the base structure. |
| 282 | // If we ever change it to contain only the prototype chain, we'll also need to change |
| 283 | // this to check the base structure. |
| 284 | return structure() == other.structure() |
| 285 | && *m_polyProtoAccessChain == *other.m_polyProtoAccessChain; |
| 286 | } |
| 287 | |
| 288 | if (!guardedByStructureCheck() || !other.guardedByStructureCheck()) |
| 289 | return false; |
| 290 | |
| 291 | return structure() == other.structure(); |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | void AccessCase::dump(PrintStream& out) const |
| 296 | { |
| 297 | out.print("\n" , m_type, ":(" ); |
| 298 | |
| 299 | CommaPrinter comma; |
| 300 | |
| 301 | out.print(comma, m_state); |
| 302 | |
| 303 | if (isValidOffset(m_offset)) |
| 304 | out.print(comma, "offset = " , m_offset); |
| 305 | if (!m_conditionSet.isEmpty()) |
| 306 | out.print(comma, "conditions = " , m_conditionSet); |
| 307 | |
| 308 | if (m_polyProtoAccessChain) { |
| 309 | out.print(comma, "prototype access chain = " ); |
| 310 | m_polyProtoAccessChain->dump(structure(), out); |
| 311 | } else { |
| 312 | if (m_type == Transition) |
| 313 | out.print(comma, "structure = " , pointerDump(structure()), " -> " , pointerDump(newStructure())); |
| 314 | else if (m_structure) |
| 315 | out.print(comma, "structure = " , pointerDump(m_structure.get())); |
| 316 | } |
| 317 | |
| 318 | dumpImpl(out, comma); |
| 319 | out.print(")" ); |
| 320 | } |
| 321 | |
| 322 | bool AccessCase::visitWeak(VM& vm) const |
| 323 | { |
| 324 | if (m_structure && !vm.heap.isMarked(m_structure.get())) |
| 325 | return false; |
| 326 | if (m_polyProtoAccessChain) { |
| 327 | for (Structure* structure : m_polyProtoAccessChain->chain()) { |
| 328 | if (!vm.heap.isMarked(structure)) |
| 329 | return false; |
| 330 | } |
| 331 | } |
| 332 | if (!m_conditionSet.areStillLive(vm)) |
| 333 | return false; |
| 334 | if (isAccessor()) { |
| 335 | auto& accessor = this->as<GetterSetterAccessCase>(); |
| 336 | if (accessor.callLinkInfo()) |
| 337 | accessor.callLinkInfo()->visitWeak(vm); |
| 338 | if (accessor.customSlotBase() && !vm.heap.isMarked(accessor.customSlotBase())) |
| 339 | return false; |
| 340 | } else if (type() == IntrinsicGetter) { |
| 341 | auto& intrinsic = this->as<IntrinsicGetterAccessCase>(); |
| 342 | if (intrinsic.intrinsicFunction() && !vm.heap.isMarked(intrinsic.intrinsicFunction())) |
| 343 | return false; |
| 344 | } else if (type() == ModuleNamespaceLoad) { |
| 345 | auto& accessCase = this->as<ModuleNamespaceAccessCase>(); |
| 346 | if (accessCase.moduleNamespaceObject() && !vm.heap.isMarked(accessCase.moduleNamespaceObject())) |
| 347 | return false; |
| 348 | if (accessCase.moduleEnvironment() && !vm.heap.isMarked(accessCase.moduleEnvironment())) |
| 349 | return false; |
| 350 | } else if (type() == InstanceOfHit || type() == InstanceOfMiss) { |
| 351 | if (as<InstanceOfAccessCase>().prototype() && !vm.heap.isMarked(as<InstanceOfAccessCase>().prototype())) |
| 352 | return false; |
| 353 | } |
| 354 | |
| 355 | return true; |
| 356 | } |
| 357 | |
| 358 | bool AccessCase::propagateTransitions(SlotVisitor& visitor) const |
| 359 | { |
| 360 | bool result = true; |
| 361 | |
| 362 | if (m_structure) |
| 363 | result &= m_structure->markIfCheap(visitor); |
| 364 | |
| 365 | if (m_polyProtoAccessChain) { |
| 366 | for (Structure* structure : m_polyProtoAccessChain->chain()) |
| 367 | result &= structure->markIfCheap(visitor); |
| 368 | } |
| 369 | |
| 370 | switch (m_type) { |
| 371 | case Transition: |
| 372 | if (visitor.vm().heap.isMarked(m_structure->previousID())) |
| 373 | visitor.appendUnbarriered(m_structure.get()); |
| 374 | else |
| 375 | result = false; |
| 376 | break; |
| 377 | default: |
| 378 | break; |
| 379 | } |
| 380 | |
| 381 | return result; |
| 382 | } |
| 383 | |
| 384 | void AccessCase::generateWithGuard( |
| 385 | AccessGenerationState& state, CCallHelpers::JumpList& fallThrough) |
| 386 | { |
| 387 | SuperSamplerScope superSamplerScope(false); |
| 388 | |
| 389 | RELEASE_ASSERT(m_state == Committed); |
| 390 | m_state = Generated; |
| 391 | |
| 392 | CCallHelpers& jit = *state.jit; |
| 393 | StructureStubInfo& stubInfo = *state.stubInfo; |
| 394 | VM& vm = state.m_vm; |
| 395 | JSValueRegs valueRegs = state.valueRegs; |
| 396 | GPRReg baseGPR = state.baseGPR; |
| 397 | GPRReg thisGPR = state.thisGPR != InvalidGPRReg ? state.thisGPR : baseGPR; |
| 398 | GPRReg scratchGPR = state.scratchGPR; |
| 399 | |
| 400 | UNUSED_PARAM(vm); |
| 401 | |
| 402 | auto emitDefaultGuard = [&] () { |
| 403 | if (m_polyProtoAccessChain) { |
| 404 | GPRReg baseForAccessGPR = state.scratchGPR; |
| 405 | jit.move(state.baseGPR, baseForAccessGPR); |
| 406 | m_polyProtoAccessChain->forEach(structure(), [&] (Structure* structure, bool atEnd) { |
| 407 | fallThrough.append( |
| 408 | jit.branchStructure( |
| 409 | CCallHelpers::NotEqual, |
| 410 | CCallHelpers::Address(baseForAccessGPR, JSCell::structureIDOffset()), |
| 411 | structure)); |
| 412 | if (atEnd) { |
| 413 | if ((m_type == Miss || m_type == InMiss || m_type == Transition) && structure->hasPolyProto()) { |
| 414 | // For a Miss/InMiss/Transition, we must ensure we're at the end when the last item is poly proto. |
| 415 | // Transitions must do this because they need to verify there isn't a setter in the chain. |
| 416 | // Miss/InMiss need to do this to ensure there isn't a new item at the end of the chain that |
| 417 | // has the property. |
| 418 | #if USE(JSVALUE64) |
| 419 | jit.load64(MacroAssembler::Address(baseForAccessGPR, offsetRelativeToBase(knownPolyProtoOffset)), baseForAccessGPR); |
| 420 | fallThrough.append(jit.branch64(CCallHelpers::NotEqual, baseForAccessGPR, CCallHelpers::TrustedImm64(ValueNull))); |
| 421 | #else |
| 422 | jit.load32(MacroAssembler::Address(baseForAccessGPR, offsetRelativeToBase(knownPolyProtoOffset) + PayloadOffset), baseForAccessGPR); |
| 423 | fallThrough.append(jit.branchTestPtr(CCallHelpers::NonZero, baseForAccessGPR)); |
| 424 | #endif |
| 425 | } |
| 426 | } else { |
| 427 | if (structure->hasMonoProto()) { |
| 428 | JSValue prototype = structure->prototypeForLookup(state.m_globalObject); |
| 429 | RELEASE_ASSERT(prototype.isObject()); |
| 430 | jit.move(CCallHelpers::TrustedImmPtr(asObject(prototype)), baseForAccessGPR); |
| 431 | } else { |
| 432 | RELEASE_ASSERT(structure->isObject()); // Primitives must have a stored prototype. We use prototypeForLookup for them. |
| 433 | #if USE(JSVALUE64) |
| 434 | jit.load64(MacroAssembler::Address(baseForAccessGPR, offsetRelativeToBase(knownPolyProtoOffset)), baseForAccessGPR); |
| 435 | fallThrough.append(jit.branch64(CCallHelpers::Equal, baseForAccessGPR, CCallHelpers::TrustedImm64(ValueNull))); |
| 436 | #else |
| 437 | jit.load32(MacroAssembler::Address(baseForAccessGPR, offsetRelativeToBase(knownPolyProtoOffset) + PayloadOffset), baseForAccessGPR); |
| 438 | fallThrough.append(jit.branchTestPtr(CCallHelpers::Zero, baseForAccessGPR)); |
| 439 | #endif |
| 440 | } |
| 441 | } |
| 442 | }); |
| 443 | return; |
| 444 | } |
| 445 | |
| 446 | if (viaProxy()) { |
| 447 | fallThrough.append( |
| 448 | jit.branchIfNotType(baseGPR, PureForwardingProxyType)); |
| 449 | |
| 450 | jit.loadPtr(CCallHelpers::Address(baseGPR, JSProxy::targetOffset()), scratchGPR); |
| 451 | |
| 452 | fallThrough.append( |
| 453 | jit.branchStructure( |
| 454 | CCallHelpers::NotEqual, |
| 455 | CCallHelpers::Address(scratchGPR, JSCell::structureIDOffset()), |
| 456 | structure())); |
| 457 | return; |
| 458 | } |
| 459 | |
| 460 | fallThrough.append( |
| 461 | jit.branchStructure( |
| 462 | CCallHelpers::NotEqual, |
| 463 | CCallHelpers::Address(baseGPR, JSCell::structureIDOffset()), |
| 464 | structure())); |
| 465 | }; |
| 466 | |
| 467 | switch (m_type) { |
| 468 | case ArrayLength: { |
| 469 | ASSERT(!viaProxy()); |
| 470 | jit.load8(CCallHelpers::Address(baseGPR, JSCell::indexingTypeAndMiscOffset()), scratchGPR); |
| 471 | fallThrough.append( |
| 472 | jit.branchTest32( |
| 473 | CCallHelpers::Zero, scratchGPR, CCallHelpers::TrustedImm32(IsArray))); |
| 474 | fallThrough.append( |
| 475 | jit.branchTest32( |
| 476 | CCallHelpers::Zero, scratchGPR, CCallHelpers::TrustedImm32(IndexingShapeMask))); |
| 477 | break; |
| 478 | } |
| 479 | |
| 480 | case StringLength: { |
| 481 | ASSERT(!viaProxy()); |
| 482 | fallThrough.append( |
| 483 | jit.branchIfNotString(baseGPR)); |
| 484 | break; |
| 485 | } |
| 486 | |
| 487 | case DirectArgumentsLength: { |
| 488 | ASSERT(!viaProxy()); |
| 489 | fallThrough.append( |
| 490 | jit.branchIfNotType(baseGPR, DirectArgumentsType)); |
| 491 | |
| 492 | fallThrough.append( |
| 493 | jit.branchTestPtr( |
| 494 | CCallHelpers::NonZero, |
| 495 | CCallHelpers::Address(baseGPR, DirectArguments::offsetOfMappedArguments()))); |
| 496 | jit.load32( |
| 497 | CCallHelpers::Address(baseGPR, DirectArguments::offsetOfLength()), |
| 498 | valueRegs.payloadGPR()); |
| 499 | jit.boxInt32(valueRegs.payloadGPR(), valueRegs); |
| 500 | state.succeed(); |
| 501 | return; |
| 502 | } |
| 503 | |
| 504 | case ScopedArgumentsLength: { |
| 505 | ASSERT(!viaProxy()); |
| 506 | fallThrough.append( |
| 507 | jit.branchIfNotType(baseGPR, ScopedArgumentsType)); |
| 508 | |
| 509 | jit.loadPtr( |
| 510 | CCallHelpers::Address(baseGPR, ScopedArguments::offsetOfStorage()), |
| 511 | scratchGPR); |
| 512 | fallThrough.append( |
| 513 | jit.branchTest8( |
| 514 | CCallHelpers::NonZero, |
| 515 | CCallHelpers::Address(scratchGPR, ScopedArguments::offsetOfOverrodeThingsInStorage()))); |
| 516 | jit.load32( |
| 517 | CCallHelpers::Address(scratchGPR, ScopedArguments::offsetOfTotalLengthInStorage()), |
| 518 | valueRegs.payloadGPR()); |
| 519 | jit.boxInt32(valueRegs.payloadGPR(), valueRegs); |
| 520 | state.succeed(); |
| 521 | return; |
| 522 | } |
| 523 | |
| 524 | case ModuleNamespaceLoad: { |
| 525 | this->as<ModuleNamespaceAccessCase>().emit(state, fallThrough); |
| 526 | return; |
| 527 | } |
| 528 | |
| 529 | case InstanceOfHit: |
| 530 | case InstanceOfMiss: |
| 531 | emitDefaultGuard(); |
| 532 | |
| 533 | fallThrough.append( |
| 534 | jit.branchPtr( |
| 535 | CCallHelpers::NotEqual, thisGPR, |
| 536 | CCallHelpers::TrustedImmPtr(as<InstanceOfAccessCase>().prototype()))); |
| 537 | break; |
| 538 | |
| 539 | case InstanceOfGeneric: { |
| 540 | // Legend: value = `base instanceof this`. |
| 541 | |
| 542 | GPRReg valueGPR = valueRegs.payloadGPR(); |
| 543 | |
| 544 | ScratchRegisterAllocator allocator(stubInfo.patch.usedRegisters); |
| 545 | allocator.lock(baseGPR); |
| 546 | allocator.lock(valueGPR); |
| 547 | allocator.lock(thisGPR); |
| 548 | allocator.lock(scratchGPR); |
| 549 | |
| 550 | GPRReg scratch2GPR = allocator.allocateScratchGPR(); |
| 551 | |
| 552 | if (!state.stubInfo->prototypeIsKnownObject) |
| 553 | state.failAndIgnore.append(jit.branchIfNotObject(thisGPR)); |
| 554 | |
| 555 | ScratchRegisterAllocator::PreservedState preservedState = |
| 556 | allocator.preserveReusedRegistersByPushing( |
| 557 | jit, |
| 558 | ScratchRegisterAllocator::ExtraStackSpace::NoExtraSpace); |
| 559 | CCallHelpers::Jump failAndIgnore; |
| 560 | |
| 561 | jit.move(baseGPR, valueGPR); |
| 562 | |
| 563 | CCallHelpers::Label loop(&jit); |
| 564 | failAndIgnore = jit.branchIfType(valueGPR, ProxyObjectType); |
| 565 | |
| 566 | jit.emitLoadStructure(vm, valueGPR, scratch2GPR, scratchGPR); |
| 567 | #if USE(JSVALUE64) |
| 568 | jit.load64(CCallHelpers::Address(scratch2GPR, Structure::prototypeOffset()), scratch2GPR); |
| 569 | CCallHelpers::Jump hasMonoProto = jit.branchTest64(CCallHelpers::NonZero, scratch2GPR); |
| 570 | jit.load64( |
| 571 | CCallHelpers::Address(valueGPR, offsetRelativeToBase(knownPolyProtoOffset)), |
| 572 | scratch2GPR); |
| 573 | hasMonoProto.link(&jit); |
| 574 | #else |
| 575 | jit.load32( |
| 576 | CCallHelpers::Address(scratch2GPR, Structure::prototypeOffset() + TagOffset), |
| 577 | scratchGPR); |
| 578 | jit.load32( |
| 579 | CCallHelpers::Address(scratch2GPR, Structure::prototypeOffset() + PayloadOffset), |
| 580 | scratch2GPR); |
| 581 | CCallHelpers::Jump hasMonoProto = jit.branch32( |
| 582 | CCallHelpers::NotEqual, scratchGPR, CCallHelpers::TrustedImm32(JSValue::EmptyValueTag)); |
| 583 | jit.load32( |
| 584 | CCallHelpers::Address( |
| 585 | valueGPR, offsetRelativeToBase(knownPolyProtoOffset) + PayloadOffset), |
| 586 | scratch2GPR); |
| 587 | hasMonoProto.link(&jit); |
| 588 | #endif |
| 589 | jit.move(scratch2GPR, valueGPR); |
| 590 | |
| 591 | CCallHelpers::Jump isInstance = jit.branchPtr(CCallHelpers::Equal, valueGPR, thisGPR); |
| 592 | |
| 593 | #if USE(JSVALUE64) |
| 594 | jit.branchIfCell(JSValueRegs(valueGPR)).linkTo(loop, &jit); |
| 595 | #else |
| 596 | jit.branchTestPtr(CCallHelpers::NonZero, valueGPR).linkTo(loop, &jit); |
| 597 | #endif |
| 598 | |
| 599 | jit.boxBooleanPayload(false, valueGPR); |
| 600 | allocator.restoreReusedRegistersByPopping(jit, preservedState); |
| 601 | state.succeed(); |
| 602 | |
| 603 | isInstance.link(&jit); |
| 604 | jit.boxBooleanPayload(true, valueGPR); |
| 605 | allocator.restoreReusedRegistersByPopping(jit, preservedState); |
| 606 | state.succeed(); |
| 607 | |
| 608 | if (allocator.didReuseRegisters()) { |
| 609 | failAndIgnore.link(&jit); |
| 610 | allocator.restoreReusedRegistersByPopping(jit, preservedState); |
| 611 | state.failAndIgnore.append(jit.jump()); |
| 612 | } else |
| 613 | state.failAndIgnore.append(failAndIgnore); |
| 614 | return; |
| 615 | } |
| 616 | |
| 617 | default: |
| 618 | emitDefaultGuard(); |
| 619 | break; |
| 620 | } |
| 621 | |
| 622 | generateImpl(state); |
| 623 | } |
| 624 | |
| 625 | void AccessCase::generate(AccessGenerationState& state) |
| 626 | { |
| 627 | RELEASE_ASSERT(m_state == Committed); |
| 628 | m_state = Generated; |
| 629 | |
| 630 | generateImpl(state); |
| 631 | } |
| 632 | |
| 633 | void AccessCase::generateImpl(AccessGenerationState& state) |
| 634 | { |
| 635 | SuperSamplerScope superSamplerScope(false); |
| 636 | if (AccessCaseInternal::verbose) |
| 637 | dataLog("\n\nGenerating code for: " , *this, "\n" ); |
| 638 | |
| 639 | ASSERT(m_state == Generated); // We rely on the callers setting this for us. |
| 640 | |
| 641 | CCallHelpers& jit = *state.jit; |
| 642 | VM& vm = state.m_vm; |
| 643 | CodeBlock* codeBlock = jit.codeBlock(); |
| 644 | StructureStubInfo& stubInfo = *state.stubInfo; |
| 645 | const Identifier& ident = *state.ident; |
| 646 | JSValueRegs valueRegs = state.valueRegs; |
| 647 | GPRReg baseGPR = state.baseGPR; |
| 648 | GPRReg thisGPR = state.thisGPR != InvalidGPRReg ? state.thisGPR : baseGPR; |
| 649 | GPRReg scratchGPR = state.scratchGPR; |
| 650 | |
| 651 | ASSERT(m_conditionSet.structuresEnsureValidityAssumingImpurePropertyWatchpoint()); |
| 652 | |
| 653 | for (const ObjectPropertyCondition& condition : m_conditionSet) { |
| 654 | RELEASE_ASSERT(!m_polyProtoAccessChain); |
| 655 | |
| 656 | Structure* structure = condition.object()->structure(vm); |
| 657 | |
| 658 | if (condition.isWatchableAssumingImpurePropertyWatchpoint()) { |
| 659 | structure->addTransitionWatchpoint(state.addWatchpoint(condition)); |
| 660 | continue; |
| 661 | } |
| 662 | |
| 663 | if (!condition.structureEnsuresValidityAssumingImpurePropertyWatchpoint(structure)) { |
| 664 | // The reason why this cannot happen is that we require that PolymorphicAccess calls |
| 665 | // AccessCase::generate() only after it has verified that |
| 666 | // AccessCase::couldStillSucceed() returned true. |
| 667 | |
| 668 | dataLog("This condition is no longer met: " , condition, "\n" ); |
| 669 | RELEASE_ASSERT_NOT_REACHED(); |
| 670 | } |
| 671 | |
| 672 | // We will emit code that has a weak reference that isn't otherwise listed anywhere. |
| 673 | state.weakReferences.append(WriteBarrier<JSCell>(vm, codeBlock, structure)); |
| 674 | |
| 675 | jit.move(CCallHelpers::TrustedImmPtr(condition.object()), scratchGPR); |
| 676 | state.failAndRepatch.append( |
| 677 | jit.branchStructure( |
| 678 | CCallHelpers::NotEqual, |
| 679 | CCallHelpers::Address(scratchGPR, JSCell::structureIDOffset()), |
| 680 | structure)); |
| 681 | } |
| 682 | |
| 683 | switch (m_type) { |
| 684 | case InHit: |
| 685 | case InMiss: |
| 686 | jit.boxBoolean(m_type == InHit, valueRegs); |
| 687 | state.succeed(); |
| 688 | return; |
| 689 | |
| 690 | case Miss: |
| 691 | jit.moveTrustedValue(jsUndefined(), valueRegs); |
| 692 | state.succeed(); |
| 693 | return; |
| 694 | |
| 695 | case InstanceOfHit: |
| 696 | case InstanceOfMiss: |
| 697 | jit.boxBooleanPayload(m_type == InstanceOfHit, valueRegs.payloadGPR()); |
| 698 | state.succeed(); |
| 699 | return; |
| 700 | |
| 701 | case Load: |
| 702 | case GetGetter: |
| 703 | case Getter: |
| 704 | case Setter: |
| 705 | case CustomValueGetter: |
| 706 | case CustomAccessorGetter: |
| 707 | case CustomValueSetter: |
| 708 | case CustomAccessorSetter: { |
| 709 | GPRReg valueRegsPayloadGPR = valueRegs.payloadGPR(); |
| 710 | |
| 711 | if (isValidOffset(m_offset)) { |
| 712 | Structure* currStructure; |
| 713 | if (!hasAlternateBase()) |
| 714 | currStructure = structure(); |
| 715 | else |
| 716 | currStructure = alternateBase()->structure(vm); |
| 717 | currStructure->startWatchingPropertyForReplacements(vm, offset()); |
| 718 | } |
| 719 | |
| 720 | GPRReg baseForGetGPR; |
| 721 | if (viaProxy()) { |
| 722 | ASSERT(m_type != CustomValueSetter || m_type != CustomAccessorSetter); // Because setters need to not trash valueRegsPayloadGPR. |
| 723 | if (m_type == Getter || m_type == Setter) |
| 724 | baseForGetGPR = scratchGPR; |
| 725 | else |
| 726 | baseForGetGPR = valueRegsPayloadGPR; |
| 727 | |
| 728 | ASSERT((m_type != Getter && m_type != Setter) || baseForGetGPR != baseGPR); |
| 729 | ASSERT(m_type != Setter || baseForGetGPR != valueRegsPayloadGPR); |
| 730 | |
| 731 | jit.loadPtr( |
| 732 | CCallHelpers::Address(baseGPR, JSProxy::targetOffset()), |
| 733 | baseForGetGPR); |
| 734 | } else |
| 735 | baseForGetGPR = baseGPR; |
| 736 | |
| 737 | GPRReg baseForAccessGPR; |
| 738 | if (m_polyProtoAccessChain) { |
| 739 | // This isn't pretty, but we know we got here via generateWithGuard, |
| 740 | // and it left the baseForAccess inside scratchGPR. We could re-derive the base, |
| 741 | // but it'd require emitting the same code to load the base twice. |
| 742 | baseForAccessGPR = scratchGPR; |
| 743 | } else { |
| 744 | if (hasAlternateBase()) { |
| 745 | jit.move( |
| 746 | CCallHelpers::TrustedImmPtr(alternateBase()), scratchGPR); |
| 747 | baseForAccessGPR = scratchGPR; |
| 748 | } else |
| 749 | baseForAccessGPR = baseForGetGPR; |
| 750 | } |
| 751 | |
| 752 | GPRReg loadedValueGPR = InvalidGPRReg; |
| 753 | if (m_type != CustomValueGetter && m_type != CustomAccessorGetter && m_type != CustomValueSetter && m_type != CustomAccessorSetter) { |
| 754 | if (m_type == Load || m_type == GetGetter) |
| 755 | loadedValueGPR = valueRegsPayloadGPR; |
| 756 | else |
| 757 | loadedValueGPR = scratchGPR; |
| 758 | |
| 759 | ASSERT((m_type != Getter && m_type != Setter) || loadedValueGPR != baseGPR); |
| 760 | ASSERT(m_type != Setter || loadedValueGPR != valueRegsPayloadGPR); |
| 761 | |
| 762 | GPRReg storageGPR; |
| 763 | if (isInlineOffset(m_offset)) |
| 764 | storageGPR = baseForAccessGPR; |
| 765 | else { |
| 766 | jit.loadPtr( |
| 767 | CCallHelpers::Address(baseForAccessGPR, JSObject::butterflyOffset()), |
| 768 | loadedValueGPR); |
| 769 | storageGPR = loadedValueGPR; |
| 770 | } |
| 771 | |
| 772 | #if USE(JSVALUE64) |
| 773 | jit.load64( |
| 774 | CCallHelpers::Address(storageGPR, offsetRelativeToBase(m_offset)), loadedValueGPR); |
| 775 | #else |
| 776 | if (m_type == Load || m_type == GetGetter) { |
| 777 | jit.load32( |
| 778 | CCallHelpers::Address(storageGPR, offsetRelativeToBase(m_offset) + TagOffset), |
| 779 | valueRegs.tagGPR()); |
| 780 | } |
| 781 | jit.load32( |
| 782 | CCallHelpers::Address(storageGPR, offsetRelativeToBase(m_offset) + PayloadOffset), |
| 783 | loadedValueGPR); |
| 784 | #endif |
| 785 | } |
| 786 | |
| 787 | if (m_type == Load || m_type == GetGetter) { |
| 788 | state.succeed(); |
| 789 | return; |
| 790 | } |
| 791 | |
| 792 | if (m_type == CustomAccessorGetter && this->as<GetterSetterAccessCase>().domAttribute()) { |
| 793 | auto& access = this->as<GetterSetterAccessCase>(); |
| 794 | // We do not need to emit CheckDOM operation since structure check ensures |
| 795 | // that the structure of the given base value is structure()! So all we should |
| 796 | // do is performing the CheckDOM thingy in IC compiling time here. |
| 797 | if (!structure()->classInfo()->isSubClassOf(access.domAttribute()->classInfo)) { |
| 798 | state.failAndIgnore.append(jit.jump()); |
| 799 | return; |
| 800 | } |
| 801 | |
| 802 | if (Options::useDOMJIT() && access.domAttribute()->domJIT) { |
| 803 | access.emitDOMJITGetter(state, access.domAttribute()->domJIT, baseForGetGPR); |
| 804 | return; |
| 805 | } |
| 806 | } |
| 807 | |
| 808 | // Stuff for custom getters/setters. |
| 809 | CCallHelpers::Call operationCall; |
| 810 | |
| 811 | // Stuff for JS getters/setters. |
| 812 | CCallHelpers::DataLabelPtr addressOfLinkFunctionCheck; |
| 813 | CCallHelpers::Call fastPathCall; |
| 814 | CCallHelpers::Call slowPathCall; |
| 815 | |
| 816 | // This also does the necessary calculations of whether or not we're an |
| 817 | // exception handling call site. |
| 818 | AccessGenerationState::SpillState spillState = state.preserveLiveRegistersToStackForCall(); |
| 819 | |
| 820 | auto restoreLiveRegistersFromStackForCall = [&](AccessGenerationState::SpillState& spillState, bool callHasReturnValue) { |
| 821 | RegisterSet dontRestore; |
| 822 | if (callHasReturnValue) { |
| 823 | // This is the result value. We don't want to overwrite the result with what we stored to the stack. |
| 824 | // We sometimes have to store it to the stack just in case we throw an exception and need the original value. |
| 825 | dontRestore.set(valueRegs); |
| 826 | } |
| 827 | state.restoreLiveRegistersFromStackForCall(spillState, dontRestore); |
| 828 | }; |
| 829 | |
| 830 | jit.store32( |
| 831 | CCallHelpers::TrustedImm32(state.callSiteIndexForExceptionHandlingOrOriginal().bits()), |
| 832 | CCallHelpers::tagFor(static_cast<VirtualRegister>(CallFrameSlot::argumentCount))); |
| 833 | |
| 834 | if (m_type == Getter || m_type == Setter) { |
| 835 | auto& access = this->as<GetterSetterAccessCase>(); |
| 836 | ASSERT(baseGPR != loadedValueGPR); |
| 837 | ASSERT(m_type != Setter || valueRegsPayloadGPR != loadedValueGPR); |
| 838 | |
| 839 | // Create a JS call using a JS call inline cache. Assume that: |
| 840 | // |
| 841 | // - SP is aligned and represents the extent of the calling compiler's stack usage. |
| 842 | // |
| 843 | // - FP is set correctly (i.e. it points to the caller's call frame header). |
| 844 | // |
| 845 | // - SP - FP is an aligned difference. |
| 846 | // |
| 847 | // - Any byte between FP (exclusive) and SP (inclusive) could be live in the calling |
| 848 | // code. |
| 849 | // |
| 850 | // Therefore, we temporarily grow the stack for the purpose of the call and then |
| 851 | // shrink it after. |
| 852 | |
| 853 | state.setSpillStateForJSGetterSetter(spillState); |
| 854 | |
| 855 | RELEASE_ASSERT(!access.callLinkInfo()); |
| 856 | access.m_callLinkInfo = std::make_unique<CallLinkInfo>(); |
| 857 | |
| 858 | // FIXME: If we generated a polymorphic call stub that jumped back to the getter |
| 859 | // stub, which then jumped back to the main code, then we'd have a reachability |
| 860 | // situation that the GC doesn't know about. The GC would ensure that the polymorphic |
| 861 | // call stub stayed alive, and it would ensure that the main code stayed alive, but |
| 862 | // it wouldn't know that the getter stub was alive. Ideally JIT stub routines would |
| 863 | // be GC objects, and then we'd be able to say that the polymorphic call stub has a |
| 864 | // reference to the getter stub. |
| 865 | // https://bugs.webkit.org/show_bug.cgi?id=148914 |
| 866 | access.callLinkInfo()->disallowStubs(); |
| 867 | |
| 868 | access.callLinkInfo()->setUpCall( |
| 869 | CallLinkInfo::Call, stubInfo.codeOrigin, loadedValueGPR); |
| 870 | |
| 871 | CCallHelpers::JumpList done; |
| 872 | |
| 873 | // There is a "this" argument. |
| 874 | unsigned numberOfParameters = 1; |
| 875 | // ... and a value argument if we're calling a setter. |
| 876 | if (m_type == Setter) |
| 877 | numberOfParameters++; |
| 878 | |
| 879 | // Get the accessor; if there ain't one then the result is jsUndefined(). |
| 880 | if (m_type == Setter) { |
| 881 | jit.loadPtr( |
| 882 | CCallHelpers::Address(loadedValueGPR, GetterSetter::offsetOfSetter()), |
| 883 | loadedValueGPR); |
| 884 | } else { |
| 885 | jit.loadPtr( |
| 886 | CCallHelpers::Address(loadedValueGPR, GetterSetter::offsetOfGetter()), |
| 887 | loadedValueGPR); |
| 888 | } |
| 889 | |
| 890 | CCallHelpers::Jump returnUndefined = jit.branchTestPtr( |
| 891 | CCallHelpers::Zero, loadedValueGPR); |
| 892 | |
| 893 | unsigned numberOfRegsForCall = CallFrame::headerSizeInRegisters + numberOfParameters; |
| 894 | unsigned numberOfBytesForCall = numberOfRegsForCall * sizeof(Register) - sizeof(CallerFrameAndPC); |
| 895 | |
| 896 | unsigned alignedNumberOfBytesForCall = |
| 897 | WTF::roundUpToMultipleOf(stackAlignmentBytes(), numberOfBytesForCall); |
| 898 | |
| 899 | jit.subPtr( |
| 900 | CCallHelpers::TrustedImm32(alignedNumberOfBytesForCall), |
| 901 | CCallHelpers::stackPointerRegister); |
| 902 | |
| 903 | CCallHelpers::Address calleeFrame = CCallHelpers::Address( |
| 904 | CCallHelpers::stackPointerRegister, |
| 905 | -static_cast<ptrdiff_t>(sizeof(CallerFrameAndPC))); |
| 906 | |
| 907 | jit.store32( |
| 908 | CCallHelpers::TrustedImm32(numberOfParameters), |
| 909 | calleeFrame.withOffset(CallFrameSlot::argumentCount * sizeof(Register) + PayloadOffset)); |
| 910 | |
| 911 | jit.storeCell( |
| 912 | loadedValueGPR, calleeFrame.withOffset(CallFrameSlot::callee * sizeof(Register))); |
| 913 | |
| 914 | jit.storeCell( |
| 915 | thisGPR, |
| 916 | calleeFrame.withOffset(virtualRegisterForArgument(0).offset() * sizeof(Register))); |
| 917 | |
| 918 | if (m_type == Setter) { |
| 919 | jit.storeValue( |
| 920 | valueRegs, |
| 921 | calleeFrame.withOffset( |
| 922 | virtualRegisterForArgument(1).offset() * sizeof(Register))); |
| 923 | } |
| 924 | |
| 925 | CCallHelpers::Jump slowCase = jit.branchPtrWithPatch( |
| 926 | CCallHelpers::NotEqual, loadedValueGPR, addressOfLinkFunctionCheck, |
| 927 | CCallHelpers::TrustedImmPtr(nullptr)); |
| 928 | |
| 929 | fastPathCall = jit.nearCall(); |
| 930 | if (m_type == Getter) |
| 931 | jit.setupResults(valueRegs); |
| 932 | done.append(jit.jump()); |
| 933 | |
| 934 | slowCase.link(&jit); |
| 935 | jit.move(loadedValueGPR, GPRInfo::regT0); |
| 936 | #if USE(JSVALUE32_64) |
| 937 | // We *always* know that the getter/setter, if non-null, is a cell. |
| 938 | jit.move(CCallHelpers::TrustedImm32(JSValue::CellTag), GPRInfo::regT1); |
| 939 | #endif |
| 940 | jit.move(CCallHelpers::TrustedImmPtr(access.callLinkInfo()), GPRInfo::regT2); |
| 941 | slowPathCall = jit.nearCall(); |
| 942 | if (m_type == Getter) |
| 943 | jit.setupResults(valueRegs); |
| 944 | done.append(jit.jump()); |
| 945 | |
| 946 | returnUndefined.link(&jit); |
| 947 | if (m_type == Getter) |
| 948 | jit.moveTrustedValue(jsUndefined(), valueRegs); |
| 949 | |
| 950 | done.link(&jit); |
| 951 | |
| 952 | jit.addPtr(CCallHelpers::TrustedImm32((codeBlock->stackPointerOffset() * sizeof(Register)) - state.preservedReusedRegisterState.numberOfBytesPreserved - spillState.numberOfStackBytesUsedForRegisterPreservation), |
| 953 | GPRInfo::callFrameRegister, CCallHelpers::stackPointerRegister); |
| 954 | bool callHasReturnValue = isGetter(); |
| 955 | restoreLiveRegistersFromStackForCall(spillState, callHasReturnValue); |
| 956 | |
| 957 | jit.addLinkTask([=, &vm] (LinkBuffer& linkBuffer) { |
| 958 | this->as<GetterSetterAccessCase>().callLinkInfo()->setCallLocations( |
| 959 | CodeLocationLabel<JSInternalPtrTag>(linkBuffer.locationOfNearCall<JSInternalPtrTag>(slowPathCall)), |
| 960 | CodeLocationLabel<JSInternalPtrTag>(linkBuffer.locationOf<JSInternalPtrTag>(addressOfLinkFunctionCheck)), |
| 961 | linkBuffer.locationOfNearCall<JSInternalPtrTag>(fastPathCall)); |
| 962 | |
| 963 | linkBuffer.link( |
| 964 | slowPathCall, |
| 965 | CodeLocationLabel<JITThunkPtrTag>(vm.getCTIStub(linkCallThunkGenerator).code())); |
| 966 | }); |
| 967 | } else { |
| 968 | ASSERT(m_type == CustomValueGetter || m_type == CustomAccessorGetter || m_type == CustomValueSetter || m_type == CustomAccessorSetter); |
| 969 | |
| 970 | // Need to make room for the C call so any of our stack spillage isn't overwritten. It's |
| 971 | // hard to track if someone did spillage or not, so we just assume that we always need |
| 972 | // to make some space here. |
| 973 | jit.makeSpaceOnStackForCCall(); |
| 974 | |
| 975 | // Check if it is a super access |
| 976 | GPRReg baseForCustomGetGPR = baseGPR != thisGPR ? thisGPR : baseForGetGPR; |
| 977 | |
| 978 | // getter: EncodedJSValue (*GetValueFunc)(ExecState*, EncodedJSValue thisValue, PropertyName); |
| 979 | // setter: void (*PutValueFunc)(ExecState*, EncodedJSValue thisObject, EncodedJSValue value); |
| 980 | // Custom values are passed the slotBase (the property holder), custom accessors are passed the thisVaule (reciever). |
| 981 | // FIXME: Remove this differences in custom values and custom accessors. |
| 982 | // https://bugs.webkit.org/show_bug.cgi?id=158014 |
| 983 | GPRReg baseForCustom = m_type == CustomValueGetter || m_type == CustomValueSetter ? baseForAccessGPR : baseForCustomGetGPR; |
| 984 | if (m_type == CustomValueGetter || m_type == CustomAccessorGetter) { |
| 985 | jit.setupArguments<PropertySlot::GetValueFunc>( |
| 986 | CCallHelpers::CellValue(baseForCustom), |
| 987 | CCallHelpers::TrustedImmPtr(ident.impl())); |
| 988 | } else { |
| 989 | jit.setupArguments<PutPropertySlot::PutValueFunc>( |
| 990 | CCallHelpers::CellValue(baseForCustom), |
| 991 | valueRegs); |
| 992 | } |
| 993 | jit.storePtr(GPRInfo::callFrameRegister, &vm.topCallFrame); |
| 994 | |
| 995 | operationCall = jit.call(OperationPtrTag); |
| 996 | jit.addLinkTask([=] (LinkBuffer& linkBuffer) { |
| 997 | linkBuffer.link(operationCall, this->as<GetterSetterAccessCase>().m_customAccessor); |
| 998 | }); |
| 999 | |
| 1000 | if (m_type == CustomValueGetter || m_type == CustomAccessorGetter) |
| 1001 | jit.setupResults(valueRegs); |
| 1002 | jit.reclaimSpaceOnStackForCCall(); |
| 1003 | |
| 1004 | CCallHelpers::Jump noException = |
| 1005 | jit.emitExceptionCheck(vm, CCallHelpers::InvertedExceptionCheck); |
| 1006 | |
| 1007 | state.restoreLiveRegistersFromStackForCallWithThrownException(spillState); |
| 1008 | state.emitExplicitExceptionHandler(); |
| 1009 | |
| 1010 | noException.link(&jit); |
| 1011 | bool callHasReturnValue = isGetter(); |
| 1012 | restoreLiveRegistersFromStackForCall(spillState, callHasReturnValue); |
| 1013 | } |
| 1014 | state.succeed(); |
| 1015 | return; |
| 1016 | } |
| 1017 | |
| 1018 | case Replace: { |
| 1019 | if (isInlineOffset(m_offset)) { |
| 1020 | jit.storeValue( |
| 1021 | valueRegs, |
| 1022 | CCallHelpers::Address( |
| 1023 | baseGPR, |
| 1024 | JSObject::offsetOfInlineStorage() + |
| 1025 | offsetInInlineStorage(m_offset) * sizeof(JSValue))); |
| 1026 | } else { |
| 1027 | jit.loadPtr(CCallHelpers::Address(baseGPR, JSObject::butterflyOffset()), scratchGPR); |
| 1028 | jit.storeValue( |
| 1029 | valueRegs, |
| 1030 | CCallHelpers::Address( |
| 1031 | scratchGPR, offsetInButterfly(m_offset) * sizeof(JSValue))); |
| 1032 | } |
| 1033 | state.succeed(); |
| 1034 | return; |
| 1035 | } |
| 1036 | |
| 1037 | case Transition: { |
| 1038 | // AccessCase::transition() should have returned null if this wasn't true. |
| 1039 | RELEASE_ASSERT(GPRInfo::numberOfRegisters >= 6 || !structure()->outOfLineCapacity() || structure()->outOfLineCapacity() == newStructure()->outOfLineCapacity()); |
| 1040 | |
| 1041 | // NOTE: This logic is duplicated in AccessCase::doesCalls(). It's important that doesCalls() knows |
| 1042 | // exactly when this would make calls. |
| 1043 | bool allocating = newStructure()->outOfLineCapacity() != structure()->outOfLineCapacity(); |
| 1044 | bool reallocating = allocating && structure()->outOfLineCapacity(); |
| 1045 | bool allocatingInline = allocating && !structure()->couldHaveIndexingHeader(); |
| 1046 | |
| 1047 | ScratchRegisterAllocator allocator(stubInfo.patch.usedRegisters); |
| 1048 | allocator.lock(baseGPR); |
| 1049 | #if USE(JSVALUE32_64) |
| 1050 | allocator.lock(stubInfo.patch.baseTagGPR); |
| 1051 | #endif |
| 1052 | allocator.lock(valueRegs); |
| 1053 | allocator.lock(scratchGPR); |
| 1054 | |
| 1055 | GPRReg scratchGPR2 = InvalidGPRReg; |
| 1056 | GPRReg scratchGPR3 = InvalidGPRReg; |
| 1057 | if (allocatingInline) { |
| 1058 | scratchGPR2 = allocator.allocateScratchGPR(); |
| 1059 | scratchGPR3 = allocator.allocateScratchGPR(); |
| 1060 | } |
| 1061 | |
| 1062 | ScratchRegisterAllocator::PreservedState preservedState = |
| 1063 | allocator.preserveReusedRegistersByPushing(jit, ScratchRegisterAllocator::ExtraStackSpace::SpaceForCCall); |
| 1064 | |
| 1065 | CCallHelpers::JumpList slowPath; |
| 1066 | |
| 1067 | ASSERT(structure()->transitionWatchpointSetHasBeenInvalidated()); |
| 1068 | |
| 1069 | if (allocating) { |
| 1070 | size_t newSize = newStructure()->outOfLineCapacity() * sizeof(JSValue); |
| 1071 | |
| 1072 | if (allocatingInline) { |
| 1073 | Allocator allocator = vm.jsValueGigacageAuxiliarySpace.allocatorFor(newSize, AllocatorForMode::AllocatorIfExists); |
| 1074 | |
| 1075 | jit.emitAllocate(scratchGPR, JITAllocator::constant(allocator), scratchGPR2, scratchGPR3, slowPath); |
| 1076 | jit.addPtr(CCallHelpers::TrustedImm32(newSize + sizeof(IndexingHeader)), scratchGPR); |
| 1077 | |
| 1078 | size_t oldSize = structure()->outOfLineCapacity() * sizeof(JSValue); |
| 1079 | ASSERT(newSize > oldSize); |
| 1080 | |
| 1081 | if (reallocating) { |
| 1082 | // Handle the case where we are reallocating (i.e. the old structure/butterfly |
| 1083 | // already had out-of-line property storage). |
| 1084 | |
| 1085 | jit.loadPtr(CCallHelpers::Address(baseGPR, JSObject::butterflyOffset()), scratchGPR3); |
| 1086 | |
| 1087 | // We have scratchGPR = new storage, scratchGPR3 = old storage, |
| 1088 | // scratchGPR2 = available |
| 1089 | for (size_t offset = 0; offset < oldSize; offset += sizeof(void*)) { |
| 1090 | jit.loadPtr( |
| 1091 | CCallHelpers::Address( |
| 1092 | scratchGPR3, |
| 1093 | -static_cast<ptrdiff_t>( |
| 1094 | offset + sizeof(JSValue) + sizeof(void*))), |
| 1095 | scratchGPR2); |
| 1096 | jit.storePtr( |
| 1097 | scratchGPR2, |
| 1098 | CCallHelpers::Address( |
| 1099 | scratchGPR, |
| 1100 | -static_cast<ptrdiff_t>(offset + sizeof(JSValue) + sizeof(void*)))); |
| 1101 | } |
| 1102 | } |
| 1103 | |
| 1104 | for (size_t offset = oldSize; offset < newSize; offset += sizeof(void*)) |
| 1105 | jit.storePtr(CCallHelpers::TrustedImmPtr(nullptr), CCallHelpers::Address(scratchGPR, -static_cast<ptrdiff_t>(offset + sizeof(JSValue) + sizeof(void*)))); |
| 1106 | } else { |
| 1107 | // Handle the case where we are allocating out-of-line using an operation. |
| 1108 | RegisterSet ; |
| 1109 | extraRegistersToPreserve.set(baseGPR); |
| 1110 | extraRegistersToPreserve.set(valueRegs); |
| 1111 | AccessGenerationState::SpillState spillState = state.preserveLiveRegistersToStackForCall(extraRegistersToPreserve); |
| 1112 | |
| 1113 | jit.store32( |
| 1114 | CCallHelpers::TrustedImm32( |
| 1115 | state.callSiteIndexForExceptionHandlingOrOriginal().bits()), |
| 1116 | CCallHelpers::tagFor(static_cast<VirtualRegister>(CallFrameSlot::argumentCount))); |
| 1117 | |
| 1118 | jit.makeSpaceOnStackForCCall(); |
| 1119 | |
| 1120 | if (!reallocating) { |
| 1121 | jit.setupArguments<decltype(operationReallocateButterflyToHavePropertyStorageWithInitialCapacity)>(baseGPR); |
| 1122 | |
| 1123 | CCallHelpers::Call operationCall = jit.call(OperationPtrTag); |
| 1124 | jit.addLinkTask([=] (LinkBuffer& linkBuffer) { |
| 1125 | linkBuffer.link( |
| 1126 | operationCall, |
| 1127 | FunctionPtr<OperationPtrTag>(operationReallocateButterflyToHavePropertyStorageWithInitialCapacity)); |
| 1128 | }); |
| 1129 | } else { |
| 1130 | // Handle the case where we are reallocating (i.e. the old structure/butterfly |
| 1131 | // already had out-of-line property storage). |
| 1132 | jit.setupArguments<decltype(operationReallocateButterflyToGrowPropertyStorage)>( |
| 1133 | baseGPR, CCallHelpers::TrustedImm32(newSize / sizeof(JSValue))); |
| 1134 | |
| 1135 | CCallHelpers::Call operationCall = jit.call(OperationPtrTag); |
| 1136 | jit.addLinkTask([=] (LinkBuffer& linkBuffer) { |
| 1137 | linkBuffer.link( |
| 1138 | operationCall, |
| 1139 | FunctionPtr<OperationPtrTag>(operationReallocateButterflyToGrowPropertyStorage)); |
| 1140 | }); |
| 1141 | } |
| 1142 | |
| 1143 | jit.reclaimSpaceOnStackForCCall(); |
| 1144 | jit.move(GPRInfo::returnValueGPR, scratchGPR); |
| 1145 | |
| 1146 | CCallHelpers::Jump noException = jit.emitExceptionCheck(vm, CCallHelpers::InvertedExceptionCheck); |
| 1147 | |
| 1148 | state.restoreLiveRegistersFromStackForCallWithThrownException(spillState); |
| 1149 | state.emitExplicitExceptionHandler(); |
| 1150 | |
| 1151 | noException.link(&jit); |
| 1152 | RegisterSet resultRegisterToExclude; |
| 1153 | resultRegisterToExclude.set(scratchGPR); |
| 1154 | state.restoreLiveRegistersFromStackForCall(spillState, resultRegisterToExclude); |
| 1155 | } |
| 1156 | } |
| 1157 | |
| 1158 | if (isInlineOffset(m_offset)) { |
| 1159 | jit.storeValue( |
| 1160 | valueRegs, |
| 1161 | CCallHelpers::Address( |
| 1162 | baseGPR, |
| 1163 | JSObject::offsetOfInlineStorage() + |
| 1164 | offsetInInlineStorage(m_offset) * sizeof(JSValue))); |
| 1165 | } else { |
| 1166 | if (!allocating) |
| 1167 | jit.loadPtr(CCallHelpers::Address(baseGPR, JSObject::butterflyOffset()), scratchGPR); |
| 1168 | jit.storeValue( |
| 1169 | valueRegs, |
| 1170 | CCallHelpers::Address(scratchGPR, offsetInButterfly(m_offset) * sizeof(JSValue))); |
| 1171 | } |
| 1172 | |
| 1173 | if (allocatingInline) { |
| 1174 | // If we were to have any indexed properties, then we would need to update the indexing mask on the base object. |
| 1175 | RELEASE_ASSERT(!newStructure()->couldHaveIndexingHeader()); |
| 1176 | // We set the new butterfly and the structure last. Doing it this way ensures that |
| 1177 | // whatever we had done up to this point is forgotten if we choose to branch to slow |
| 1178 | // path. |
| 1179 | jit.nukeStructureAndStoreButterfly(vm, scratchGPR, baseGPR); |
| 1180 | } |
| 1181 | |
| 1182 | uint32_t structureBits = bitwise_cast<uint32_t>(newStructure()->id()); |
| 1183 | jit.store32( |
| 1184 | CCallHelpers::TrustedImm32(structureBits), |
| 1185 | CCallHelpers::Address(baseGPR, JSCell::structureIDOffset())); |
| 1186 | |
| 1187 | allocator.restoreReusedRegistersByPopping(jit, preservedState); |
| 1188 | state.succeed(); |
| 1189 | |
| 1190 | // We will have a slow path if we were allocating without the help of an operation. |
| 1191 | if (allocatingInline) { |
| 1192 | if (allocator.didReuseRegisters()) { |
| 1193 | slowPath.link(&jit); |
| 1194 | allocator.restoreReusedRegistersByPopping(jit, preservedState); |
| 1195 | state.failAndIgnore.append(jit.jump()); |
| 1196 | } else |
| 1197 | state.failAndIgnore.append(slowPath); |
| 1198 | } else |
| 1199 | RELEASE_ASSERT(slowPath.empty()); |
| 1200 | return; |
| 1201 | } |
| 1202 | |
| 1203 | case ArrayLength: { |
| 1204 | jit.loadPtr(CCallHelpers::Address(baseGPR, JSObject::butterflyOffset()), scratchGPR); |
| 1205 | jit.load32(CCallHelpers::Address(scratchGPR, ArrayStorage::lengthOffset()), scratchGPR); |
| 1206 | state.failAndIgnore.append( |
| 1207 | jit.branch32(CCallHelpers::LessThan, scratchGPR, CCallHelpers::TrustedImm32(0))); |
| 1208 | jit.boxInt32(scratchGPR, valueRegs); |
| 1209 | state.succeed(); |
| 1210 | return; |
| 1211 | } |
| 1212 | |
| 1213 | case StringLength: { |
| 1214 | jit.loadPtr(CCallHelpers::Address(baseGPR, JSString::offsetOfValue()), scratchGPR); |
| 1215 | auto isRope = jit.branchIfRopeStringImpl(scratchGPR); |
| 1216 | jit.load32(CCallHelpers::Address(scratchGPR, StringImpl::lengthMemoryOffset()), valueRegs.payloadGPR()); |
| 1217 | auto done = jit.jump(); |
| 1218 | |
| 1219 | isRope.link(&jit); |
| 1220 | jit.load32(CCallHelpers::Address(baseGPR, JSRopeString::offsetOfLength()), valueRegs.payloadGPR()); |
| 1221 | |
| 1222 | done.link(&jit); |
| 1223 | jit.boxInt32(valueRegs.payloadGPR(), valueRegs); |
| 1224 | state.succeed(); |
| 1225 | return; |
| 1226 | } |
| 1227 | |
| 1228 | case IntrinsicGetter: { |
| 1229 | RELEASE_ASSERT(isValidOffset(offset())); |
| 1230 | |
| 1231 | // We need to ensure the getter value does not move from under us. Note that GetterSetters |
| 1232 | // are immutable so we just need to watch the property not any value inside it. |
| 1233 | Structure* currStructure; |
| 1234 | if (!hasAlternateBase()) |
| 1235 | currStructure = structure(); |
| 1236 | else |
| 1237 | currStructure = alternateBase()->structure(vm); |
| 1238 | currStructure->startWatchingPropertyForReplacements(vm, offset()); |
| 1239 | |
| 1240 | this->as<IntrinsicGetterAccessCase>().emitIntrinsicGetter(state); |
| 1241 | return; |
| 1242 | } |
| 1243 | |
| 1244 | case DirectArgumentsLength: |
| 1245 | case ScopedArgumentsLength: |
| 1246 | case ModuleNamespaceLoad: |
| 1247 | case InstanceOfGeneric: |
| 1248 | // These need to be handled by generateWithGuard(), since the guard is part of the |
| 1249 | // algorithm. We can be sure that nobody will call generate() directly for these since they |
| 1250 | // are not guarded by structure checks. |
| 1251 | RELEASE_ASSERT_NOT_REACHED(); |
| 1252 | } |
| 1253 | |
| 1254 | RELEASE_ASSERT_NOT_REACHED(); |
| 1255 | } |
| 1256 | |
| 1257 | } // namespace JSC |
| 1258 | |
| 1259 | #endif |
| 1260 | |