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 | |