forked from WebKit/WebKit
-
Notifications
You must be signed in to change notification settings - Fork 56
Expand file tree
/
Copy pathPropertyInlineCache.h
More file actions
1157 lines (1045 loc) · 53.7 KB
/
Copy pathPropertyInlineCache.h
File metadata and controls
1157 lines (1045 loc) · 53.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (C) 2008-2026 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "CacheableIdentifier.h"
#include "CodeBlock.h"
#include "CodeOrigin.h"
#include "InlineCacheCompiler.h"
#include "JITStubRoutine.h"
#include "MacroAssembler.h"
#include "Options.h"
#include "PropertyInlineCacheClearingWatchpoint.h"
#include "PropertyInlineCacheSummary.h"
#include "RegisterSet.h"
#include "Structure.h"
#include <atomic>
#include <wtf/Atomics.h>
#include <wtf/Lock.h>
#include <wtf/TZoneMalloc.h>
namespace JSC {
// icConcurrentRelaxed{Load,Store}: generic relaxed-atomic accessors over plain
// storage. Declared UNCONDITIONALLY (outside the ENABLE(JIT) guard below) so
// the InterpreterThunk arm of CodeBlock::propagateTransitions — interpreter
// code that compiles in the ENABLE_JIT=OFF / ENABLE_C_LOOP=ON TSAN config —
// can use them. The full rationale comment lives at the (now-removed)
// original definition site below; mirrors Structure.h §9.1.
template<typename T>
ALWAYS_INLINE T icConcurrentRelaxedLoad(const T& field)
{
static_assert(std::is_trivially_copyable_v<T>);
T result;
__atomic_load(const_cast<T*>(&field), &result, __ATOMIC_RELAXED);
return result;
}
template<typename T>
ALWAYS_INLINE void icConcurrentRelaxedStore(T& field, std::type_identity_t<T> value)
{
static_assert(std::is_trivially_copyable_v<T>);
__atomic_store(&field, &value, __ATOMIC_RELAXED);
}
} // namespace JSC
#if ENABLE(JIT)
namespace JSC {
namespace DFG {
struct UnlinkedPropertyInlineCache;
}
class AccessCase;
class AccessGenerationResult;
class PolymorphicAccess;
enum class DisarmClearingWatchpoints : bool; // Defined in RetiredJITArtifacts.h.
#define JSC_FOR_EACH_PROPERTY_INLINE_CACHE_ACCESS_TYPE(macro) \
macro(GetById) \
macro(GetByIdWithThis) \
macro(GetByIdDirect) \
macro(TryGetById) \
macro(GetByVal) \
macro(GetByValWithThis) \
macro(PutByIdStrict) \
macro(PutByIdSloppy) \
macro(PutByIdDirectStrict) \
macro(PutByIdDirectSloppy) \
macro(PutByValStrict) \
macro(PutByValSloppy) \
macro(PutByValDirectStrict) \
macro(PutByValDirectSloppy) \
macro(DefinePrivateNameByVal) \
macro(DefinePrivateNameById) \
macro(SetPrivateNameByVal) \
macro(SetPrivateNameById) \
macro(InById) \
macro(InByVal) \
macro(HasPrivateName) \
macro(HasPrivateBrand) \
macro(InstanceOf) \
macro(DeleteByIdStrict) \
macro(DeleteByIdSloppy) \
macro(DeleteByValStrict) \
macro(DeleteByValSloppy) \
macro(GetPrivateName) \
macro(GetPrivateNameById) \
macro(CheckPrivateBrand) \
macro(SetPrivateBrand) \
enum class AccessType : int8_t {
#define JSC_DEFINE_ACCESS_TYPE(name) name,
JSC_FOR_EACH_PROPERTY_INLINE_CACHE_ACCESS_TYPE(JSC_DEFINE_ACCESS_TYPE)
#undef JSC_DEFINE_ACCESS_TYPE
};
#define JSC_INCREMENT_ACCESS_TYPE(name) + 1
static constexpr unsigned numberOfAccessTypes = 0 JSC_FOR_EACH_PROPERTY_INLINE_CACHE_ACCESS_TYPE(JSC_INCREMENT_ACCESS_TYPE);
#undef JSC_INCREMENT_ACCESS_TYPE
// This file defines two distinct inline cache (IC) dispatch strategies used across
// JSC's JIT tiers. Both strategies work the same way conceptually: each IC site is
// guarded by one or more conditions, either runtime checks (e.g. a structureID
// comparison, a property UID check) or Watchpoints on values assumed to be stable.
// When a guard passes, the IC performs the cached access; when it fails, we fall
// through to the next case or the slow path, which may add new cases.
//
// The two strategies differ in how they store and dispatch through those cases:
//
// HandlerIC: used by Baseline JIT and DFG. The call site never modifies machine
// code; instead it loads a pointer to the head of a singly-linked list of
// InlineCacheHandler nodes and dispatches through them at runtime.
//
// RepatchingIC: used by FTL only. The call site owns a fixed-size
// slab of inline machine code embedded in the compiled function body. That slab
// is rewritten at runtime as new cases are learned.
//
// The choice between them is a throughput vs. cost tradeoff. RepatchingIC can generate
// bespoke, case-specific code and, once sufficiently polymorphic, dispatch via a binary switch,
// which is meaningfully faster than walking a chain of indirect branches.
// However, rewriting machine code at runtime is expensive. So we only pay that
// cost in the FTL, where we have substantially more profiling and thus think the code
// is most likely to be in a steady state.
enum class PropertyInlineCacheType : uint8_t { Handler, Repatching };
// TSAN ic-stubinfo family (SPEC-jit §5.7.7 / D3): single-byte advisory IC
// state flag. These used to be `bool : 1` bitfields packed into one byte
// together with the const m_icType bit, but they are written from IC slow
// paths on ANY thread with no lock held (operation*GaveUp sets tookSlowPath,
// considerRepatchingCache* sets everConsidered/sawNonCell), so every bitfield
// write was a read-modify-write of the shared byte: it raced with itself
// (lost updates of NEIGHBORING flags) and paired with every lock-free reader
// of another bit in the byte — including isHandlerIC(), the
// "isHandlerIC vs operation*GaveUp" TSAN signature. Each flag is now its own
// relaxed atomic byte: same plain-mov codegen flag-off (semantics unchanged),
// no cross-flag lost updates (whole-byte store), and the concurrent accesses
// are defined. §5.7.7 blesses the value-level racing: every consumer treats
// these as advisory profiling hints.
//
// ACKNOWLEDGED FOOTPRINT COST (TSAN-TRIAGE §3.4 wave-2 amendment): the nine
// advisory flags + m_icType previously occupied 10 BITS (2 bytes); as one
// byte each they occupy 10 bytes, growing sizeof(PropertyInlineCache) by
// ~8 bytes per IC. These are trailing members: no OBJECT_OFFSETOF/JIT-emitted
// offset references any of them, and no sizeof-sensitive consumer exists
// in-tree, so the cost is memory only (tens of KB on IC-heavy workloads).
// Accepted for clarity; if the footprint ever matters, the nine mutable
// flags can be re-co-packed into one shared Atomic<uint8_t> accessed with
// relaxed load/store bit ops (cross-flag lost updates are §5.7.7-blessed —
// the old bitfield code already lost them); only m_icType must stay out of
// the racy byte, because it is const and read lock-free by isHandlerIC().
// TSAN ic-stubinfo ctor publication (TSAN-TRIAGE §10.4, campaign convention
// mirroring Structure.h §9.1 concurrentRelaxedLoad/Store): UNCONDITIONAL
// relaxed atomics over plain storage. A single-byte/single-word relaxed
// atomic load/store is the identical mov flag-off (no codegen change), and
// unlike std::atomic/WTF::Atomic members it lets CONSTRUCTION also be a
// relaxed atomic store: the std::atomic constructor initializes with a PLAIN
// store, which TSAN pairs against concurrent relaxed readers when an IC is
// (re)initialized in recycled memory or published without a synchronizing
// edge (the "DataOnly IC ctor publication" reports at the countdown bytes).
// (Definitions of icConcurrentRelaxed{Load,Store} moved above the ENABLE(JIT)
// guard so the JIT=OFF TSAN config can call them from CodeBlock.cpp.)
// Racy advisory IC state cell (SPEC-jit §5.7.7): every access — including the
// constructor's initialization — is a relaxed atomic on plain storage, so
// concurrent value-level racing is defined while flag-off codegen stays the
// plain mov the previous fields compiled to. Lost updates are tolerated by
// design (§5.7.7); JIT-emitted plain accesses via OBJECT_OFFSETOF are
// unchanged (same size, same offset; the §0 JIT-blindness tradeoff).
template<typename T>
class ICRacyCell {
public:
ICRacyCell(T value = T())
{
icConcurrentRelaxedStore(m_value, value);
}
ALWAYS_INLINE operator T() const { return icConcurrentRelaxedLoad(m_value); }
ALWAYS_INLINE ICRacyCell& operator=(T value)
{
icConcurrentRelaxedStore(m_value, value);
return *this;
}
ALWAYS_INLINE T loadRelaxed() const { return icConcurrentRelaxedLoad(m_value); }
ALWAYS_INLINE void storeRelaxed(T value) { icConcurrentRelaxedStore(m_value, value); }
private:
T m_value;
};
using ICRacyStateBool = ICRacyCell<bool>;
static_assert(sizeof(ICRacyStateBool) == 1);
static_assert(sizeof(ICRacyCell<uint8_t>) == 1);
struct UnlinkedPropertyInlineCache;
struct BaselineUnlinkedPropertyInlineCache;
class HandlerPropertyInlineCache;
class RepatchingPropertyInlineCache;
class PropertyInlineCache {
WTF_MAKE_NONCOPYABLE(PropertyInlineCache);
WTF_MAKE_TZONE_ALLOCATED(PropertyInlineCache);
public:
~PropertyInlineCache();
void initGetByIdSelf(const ConcurrentJSLockerBase&, CodeBlock*, Structure* inlineAccessBaseStructure, PropertyOffset);
void NODELETE initArrayLength(const ConcurrentJSLockerBase&);
void NODELETE initStringLength(const ConcurrentJSLockerBase&);
void initPutByIdReplace(const ConcurrentJSLockerBase&, CodeBlock*, Structure* inlineAccessBaseStructure, PropertyOffset);
void initInByIdSelf(const ConcurrentJSLockerBase&, CodeBlock*, Structure* inlineAccessBaseStructure, PropertyOffset);
AccessGenerationResult addAccessCase(const GCSafeConcurrentJSLocker&, JSGlobalObject*, CodeBlock*, ECMAMode, CacheableIdentifier, RefPtr<AccessCase>);
// AB18-E rule: the VM& is the caller's (the retiring mutator's), never
// re-derived from a cell inside — reset() retires the displaced inlined
// handler chain, and deriving the retire VM via codeBlock->vm()
// (MarkedBlock header) is the exact stale-owner pattern that produced the
// DirectCallLinkInfo::retireRecord UAF (CallLinkInfo.cpp:805).
void reset(const ConcurrentJSLockerBase&, VM&, CodeBlock*);
// Drops the IC's generated-dispatch state at jettison/destruction time.
// With useJSThreads (SPEC-jit section 5.1/section 4.4, I9) the dropped
// artifacts are routed through RetiredJITArtifacts instead of being freed
// inline, hence the VM& (for the heap's safepoint epoch).
// DisarmClearingWatchpoints (AB18-F amendment, thread-closeout final
// review): pass Yes ONLY when the owner CodeBlock is dying (~CodeBlock);
// pass No when the retired chains stay installed on a live owner
// (jettison, post-reset), so a later watched-set fire still resets the
// IC for straggler frames — see the enum comment in RetiredJITArtifacts.h.
void deref(VM&, DisarmClearingWatchpoints);
void aboutToDie();
void NODELETE initializePredefinedRegisters();
DECLARE_VISIT_AGGREGATE;
// Check if the stub has weak references that are dead. If it does, then it resets itself,
// either entirely or just enough to ensure that those dead pointers don't get used anymore.
void visitWeak(const ConcurrentJSLockerBase&, CodeBlock*);
// This returns true if it has marked everything that it will ever mark.
template<typename Visitor> void propagateTransitions(Visitor&);
PropertyInlineCacheSummary summary(const ConcurrentJSLocker&, VM&) const;
static PropertyInlineCacheSummary summary(const ConcurrentJSLocker&, VM&, const PropertyInlineCache*);
// TSAN ic-stubinfo ctor publication (TSAN-TRIAGE §10.4 "x identifier"):
// m_identifier is written once at IC initialization (initializeFrom*
// below; the IC lives inside a Baseline/DFG JITData published to racing
// mutators via CodeBlock::m_jitData with a storeStoreFence TSAN cannot
// see) and read lock-free from IC slow paths on any thread. Same
// ICRacyCell discipline as m_globalObject: relaxed atomics over the
// trivially-copyable one-word value — identical mov codegen flag-off,
// defined under the reported cross-thread pairing. Real publication
// ordering remains the owning CodeBlock's install protocol (F1/I5).
// KNOWN RESIDUAL WRITER (file owned by another workstream): the plain
// assignment in jit/JITInlineCacheGenerator.h (setUpStubInfo) still
// pairs plain-vs-atomic; convert it to setIdentifierConcurrently() when
// that file is in scope.
CacheableIdentifier identifier() const { return icConcurrentRelaxedLoad(m_identifier); }
void setIdentifierConcurrently(CacheableIdentifier value) { icConcurrentRelaxedStore(m_identifier, value); }
bool NODELETE containsPC(void* pc) const;
JSValueRegs valueRegs() const
{
return JSValueRegs(
#if USE(JSVALUE32_64)
m_valueTagGPR,
#endif
m_valueGPR);
}
JSValueRegs propertyRegs() const
{
return JSValueRegs(
#if USE(JSVALUE32_64)
propertyTagGPR(),
#endif
propertyGPR());
}
JSValueRegs baseRegs() const
{
return JSValueRegs(
#if USE(JSVALUE32_64)
m_baseTagGPR,
#endif
m_baseGPR);
}
bool thisValueIsInExtraGPR() const { return accessType == AccessType::GetByIdWithThis || accessType == AccessType::GetByValWithThis; }
bool isHandlerIC() const { return m_icType == PropertyInlineCacheType::Handler; }
#if ASSERT_ENABLED
void checkConsistency();
#else
ALWAYS_INLINE void checkConsistency() { }
#endif
CacheType cacheType() const { return m_cacheType; }
// Not ByVal and ById case: e.g. instanceof, by-index etc.
ALWAYS_INLINE bool considerRepatchingCacheGeneric(VM& vm, CodeBlock* codeBlock, Structure* structure)
{
// We never cache non-cells.
if (!structure) {
sawNonCell = true;
return false;
}
return considerRepatchingCacheImpl(vm, codeBlock, structure, CacheableIdentifier());
}
ALWAYS_INLINE bool considerRepatchingCacheBy(VM& vm, CodeBlock* codeBlock, Structure* structure, CacheableIdentifier impl)
{
// We never cache non-cells.
if (!structure) {
sawNonCell = true;
return false;
}
return considerRepatchingCacheImpl(vm, codeBlock, structure, impl);
}
ALWAYS_INLINE bool considerRepatchingCacheMegamorphic(VM& vm)
{
return considerRepatchingCacheImpl(vm, nullptr, nullptr, CacheableIdentifier());
}
// T4-ic-never-settles-giloff (alloctax2 #4): GIL-off, several tryCache*
// guards return RetryCacheLater on a condition that NEVER clears (we refuse
// to flatten dictionaries from IC paths under O2/GT11), so the slow-path
// call target stays at the Optimize operation forever and every execution
// walks considerRepatchingCacheImpl + tryCache* for nothing. The repatch*
// tail bumps this counter on each RetryCacheLater that left the IC still at
// CacheType::Unset; once it crosses a small threshold the caller repatches
// straight to the GaveUp operation (settled, cheap — same end state GIL-on
// reaches after one flatten). Relaxed-atomic, lost updates tolerated, same
// TSAN discipline as the cool-down bytes. Flag-off this is dead code (the
// repatch* tail only consults it under vm.gilOff()).
ALWAYS_INLINE void noteRetryWithoutProgress()
{
uint8_t v = retryWithoutProgressCount.loadRelaxed();
WTF::incrementWithSaturation(v);
retryWithoutProgressCount.storeRelaxed(v);
}
ALWAYS_INLINE bool shouldGiveUpPermanently() const
{
// Counter-only predicate (relaxed read, lock-free). The caller
// re-checks cacheType()==Unset UNDER codeBlock->m_lock before actually
// repatching to GaveUp, so an IC that managed at least one inline/stub
// case is never demoted; m_cacheType stays a lock-guarded field per
// the existing TSAN discipline.
unsigned threshold = static_cast<unsigned>(Options::repatchCountForCoolDown()) * 2;
return retryWithoutProgressCount.loadRelaxed() > threshold;
}
Structure* inlineAccessBaseStructure() const
{
return m_inlineAccessBaseStructureID.get();
}
CallLinkInfo* callLinkInfoAt(const ConcurrentJSLocker&, unsigned index, const AccessCase&);
Vector<AccessCase*, 16> listedAccessCases(const AbstractLocker&) const;
private:
AccessGenerationResult upgradeForPolyProtoIfNecessary(const GCSafeConcurrentJSLocker&, VM&, CodeBlock*, const Vector<AccessCase*, 16>&, AccessCase&);
ALWAYS_INLINE bool considerRepatchingCacheImpl(VM& vm, CodeBlock* codeBlock, Structure* structure, CacheableIdentifier impl)
{
AssertNoGC assertNoGC;
// This method is called from the Optimize variants of IC slow paths. The first part of this
// method tries to determine if the Optimize variant should really behave like the
// non-Optimize variant and leave the IC untouched.
//
// If we determine that we should do something to the IC then the next order of business is
// to determine if this Structure would impact the IC at all. We know that it won't, if we
// have already buffered something on its behalf. That's what the m_bufferedStructures set is
// for.
// TSAN ic-stubinfo (SPEC-jit §5.7.7): the countdown/cool-down counters
// below are advisory u8 profiling state mutated from IC slow paths on
// any thread with no lock held; all accesses are relaxed atomics so
// they are defined under concurrency, and lost updates are tolerated
// by design. Single-threaded (and flag-off) the load/modify/store
// sequences below are exactly the old plain-field logic.
everConsidered = true;
uint8_t countdownValue = countdown.loadRelaxed();
if (!countdownValue) {
// Check if we have been doing repatching too frequently. If so, then we should cool off
// for a while.
uint8_t newRepatchCount = repatchCount.loadRelaxed();
WTF::incrementWithSaturation(newRepatchCount);
repatchCount.storeRelaxed(newRepatchCount);
if (newRepatchCount > Options::repatchCountForCoolDown()) {
// We've been repatching too much, so don't do it now.
repatchCount.storeRelaxed(0);
// The amount of time we require for cool-down depends on the number of times we've
// had to cool down in the past. The relationship is exponential. The max value we
// allow here is 2^256 - 2, since the slow paths may increment the count to indicate
// that they'd like to temporarily skip patching just this once.
countdown.storeRelaxed(WTF::leftShiftWithSaturation(
static_cast<uint8_t>(Options::initialCoolDownCount()),
numberOfCoolDowns.loadRelaxed(),
static_cast<uint8_t>(std::numeric_limits<uint8_t>::max() - 1)));
uint8_t newNumberOfCoolDowns = numberOfCoolDowns.loadRelaxed();
WTF::incrementWithSaturation(newNumberOfCoolDowns);
numberOfCoolDowns.storeRelaxed(newNumberOfCoolDowns);
// We may still have had something buffered. Trigger generation now.
bufferingCountdown.storeRelaxed(0);
return true;
}
// We don't want to return false due to buffering indefinitely.
uint8_t bufferingCountdownValue = bufferingCountdown.loadRelaxed();
if (!bufferingCountdownValue) {
// Note that when this returns true, it's possible that we will not even get an
// AccessCase because this may cause Repatch.cpp to simply do an in-place
// repatching.
return true;
}
bufferingCountdown.storeRelaxed(bufferingCountdownValue - 1);
if (!structure)
return true;
// Now protect the IC buffering. We want to proceed only if this is a structure that
// we don't already have a case buffered for. Note that if this returns true but the
// bufferingCountdown is not zero then we will buffer the access case for later without
// immediately generating code for it.
//
// NOTE: This will behave oddly for InstanceOf if the user varies the prototype but not
// the base's structure. That seems unlikely for the canonical use of instanceof, where
// the prototype is fixed.
bool isNewlyAdded = false;
StructureID structureID = structure->id();
{
Locker locker { m_bufferedStructuresLock };
if (std::holds_alternative<std::monostate>(m_bufferedStructures)) {
if (identifier())
m_bufferedStructures = Vector<StructureID>();
else
m_bufferedStructures = Vector<std::tuple<StructureID, CacheableIdentifier>>();
}
WTF::switchOn(m_bufferedStructures,
[&](std::monostate) { },
[&](Vector<StructureID>& structures) {
for (auto bufferedStructureID : structures) {
if (bufferedStructureID == structureID)
return;
}
structures.append(structureID);
isNewlyAdded = true;
},
[&](Vector<std::tuple<StructureID, CacheableIdentifier>>& structures) {
ASSERT(!identifier());
for (auto& [bufferedStructureID, bufferedCacheableIdentifier] : structures) {
if (bufferedStructureID == structureID && bufferedCacheableIdentifier == impl)
return;
}
structures.append(std::tuple { structureID, impl });
isNewlyAdded = true;
});
}
if (isNewlyAdded)
vm.writeBarrier(codeBlock);
return isNewlyAdded;
}
countdown.storeRelaxed(countdownValue - 1);
return false;
}
void setCacheType(const ConcurrentJSLockerBase&, CacheType);
void clearBufferedStructures()
{
Locker locker { m_bufferedStructuresLock };
WTF::switchOn(m_bufferedStructures,
[&](std::monostate) { },
[&](Vector<StructureID>& structures) {
structures.shrink(0);
},
[&](Vector<std::tuple<StructureID, CacheableIdentifier>>& structures) {
structures.shrink(0);
});
}
protected:
PropertyInlineCache(PropertyInlineCacheType icType, AccessType accessType, CodeOrigin codeOrigin)
: codeOrigin(codeOrigin)
// SPEC-jit section 4.2: zero-initialize the packed unit; all-zero is
// the "no inlined fast path" state ({0, StructureID()} never matches).
, m_packedSelfWord(0)
, accessType(accessType)
, bufferingCountdown { static_cast<uint8_t>(Options::initialRepatchBufferingCountdown()) }
, m_icType(icType)
{
// TSAN ic-stubinfo ctor publication (§10.4): make the LAST
// constructor-time write to m_identifier a relaxed atomic store so a
// concurrent relaxed reader (identifier()) of a just-published or
// recycled IC pairs atomic-vs-atomic, mirroring the ICRacyCell
// members' rationale. The preceding implicit zero-init is overwritten
// program-order before the IC can escape this thread.
icConcurrentRelaxedStore(m_identifier, CacheableIdentifier());
}
PropertyInlineCache(PropertyInlineCacheType icType)
: PropertyInlineCache(icType, AccessType::GetById, { })
{ }
// AB18-G (extends the AB18-E rule structurally): every entry point that
// can displace-and-retire a handler chain takes the VM& from the caller's
// operation context; no retire path re-derives it via codeBlock->vm()
// (MarkedBlock header read — the stale-owner pattern behind the
// DirectCallLinkInfo::retireRecord UAF).
void initializeWithUnitHandler(VM&, CodeBlock*, Ref<InlineCacheHandler>&&);
void prependHandler(VM&, CodeBlock*, Ref<InlineCacheHandler>&&, bool isMegamorphic);
void rewireStubAsJumpInAccess(VM&, CodeBlock*, Ref<InlineCacheHandler>&&);
public:
static constexpr ptrdiff_t offsetOfByIdSelfOffset() { return OBJECT_OFFSETOF(PropertyInlineCache, byIdSelfOffset); }
static constexpr ptrdiff_t offsetOfInlineAccessBaseStructureID() { return OBJECT_OFFSETOF(PropertyInlineCache, m_inlineAccessBaseStructureID); }
static constexpr ptrdiff_t offsetOfPackedInlineAccessSelfWord() { return OBJECT_OFFSETOF(PropertyInlineCache, m_packedSelfWord); }
// SPEC-jit section 4.2 (Task 4) accessors for the inlined fast-path unit.
//
// setInlineAccessSelfState: flag-off, exactly today's per-field stores
// (WriteBarrierStructureID::set + plain offset store). Flag-on: build the
// word -> one relaxed 64-bit store via m_packedSelfWord ->
// vm.writeBarrier(codeBlock). Flag-on callers must be serialized as
// today's writers are (CodeBlock::m_lock or pre-publication init).
//
// clearInlineAccessSelfState: flag-off = m_inlineAccessBaseStructureID
// .clear() (byIdSelfOffset left stale, as today - it is unreachable once
// the id half is zero). Flag-on: one all-zero 64-bit store; barrier-free.
void setInlineAccessSelfState(VM&, CodeBlock*, Structure*, PropertyOffset);
void clearInlineAccessSelfState();
// The 64-bit memory image of {byIdSelfOffset = offset,
// m_inlineAccessBaseStructureID = structureID} on this target.
static uint64_t packedInlineAccessSelfWord(StructureID structureID, PropertyOffset offset)
{
#if CPU(LITTLE_ENDIAN)
return (static_cast<uint64_t>(structureID.bits()) << 32) | static_cast<uint32_t>(offset);
#else
return (static_cast<uint64_t>(static_cast<uint32_t>(offset)) << 32) | structureID.bits();
#endif
}
static constexpr ptrdiff_t offsetOfInlineHolder() { return OBJECT_OFFSETOF(PropertyInlineCache, m_inlineHolder); }
static constexpr ptrdiff_t offsetOfDoneLocation() { return OBJECT_OFFSETOF(PropertyInlineCache, doneLocation); }
static constexpr ptrdiff_t offsetOfCountdown() { return OBJECT_OFFSETOF(PropertyInlineCache, countdown); }
static constexpr ptrdiff_t offsetOfCallSiteIndex() { return OBJECT_OFFSETOF(PropertyInlineCache, callSiteIndex); }
static constexpr ptrdiff_t offsetOfSlowPathStartLocation() { return OBJECT_OFFSETOF(PropertyInlineCache, slowPathStartLocation); }
static constexpr ptrdiff_t offsetOfHandler() { return OBJECT_OFFSETOF(PropertyInlineCache, m_handler); }
static constexpr ptrdiff_t offsetOfGlobalObject() { return OBJECT_OFFSETOF(PropertyInlineCache, m_globalObject); }
InlineCacheHandler* firstHandler() const { return m_handler.get(); }
JSGlobalObject* globalObject() const { return m_globalObject; }
// AB18-G: VM& comes from the caller (AB18-E rule), see reset().
void resetStubAsJumpInAccess(VM&, CodeBlock*);
GPRReg thisGPR() const { return m_extraGPR; }
GPRReg prototypeGPR() const { return m_extraGPR; }
GPRReg brandGPR() const { return m_extraGPR; }
GPRReg propertyGPR() const
{
switch (accessType) {
case AccessType::GetByValWithThis:
return m_extra2GPR;
default:
return m_extraGPR;
}
}
#if USE(JSVALUE32_64)
GPRReg thisTagGPR() const { return m_extraTagGPR; }
GPRReg prototypeTagGPR() const { return m_extraTagGPR; }
GPRReg propertyTagGPR() const
{
switch (accessType) {
case AccessType::GetByValWithThis:
return m_extra2TagGPR;
default:
return m_extraTagGPR;
}
}
#endif
CodeOrigin codeOrigin { };
// SPEC-jit section 4.2 (Task 4): the inlined fast-path pair
// {byIdSelfOffset, m_inlineAccessBaseStructureID} forms one 8-byte-aligned
// unit. The repack is UNCONDITIONAL (D7): the per-field names keep exactly
// their pre-repack offsets (byIdSelfOffset at +0, the structure id at +4),
// so flag-off code - C++ and JIT emitters alike - is unchanged modulo
// nothing (same offsets, same shapes; I1). With Options::useJSThreads()
// the pair is accessed ONLY through m_packedSelfWord:
// - JIT'd readers issue ONE relaxed 64-bit load, compare the id half and
// use the offset half of that same load, so a valid structure id can
// never be observed alongside a mismatched offset (I6); compare-then-
// reload of independent fields is unsound on ARM64 (F2: compare/branch
// does not order subsequent loads).
// - Writers (serialized by CodeBlock::m_lock / IC initialization)
// publish with one 64-bit store and then issue
// vm.writeBarrier(codeBlock), preserving the GC write barrier the
// WriteBarrierStructureID::set() path provided (section 4.2).
// - Invalidation is one all-zero 64-bit store {0, StructureID()};
// structure id 0 never matches, so this is barrier-free and ABA-safe.
// GC code (visitWeak/propagateTransitions) keeps reading the id half via
// inlineAccessBaseStructureID/inlineAccessBaseStructure() as today.
// m_inlineHolder stays outside the unit: holder-bearing inlined forms
// cannot pack and are disabled flag-on (section 4.2; setInlinedHandler).
union alignas(8) {
struct {
PropertyOffset byIdSelfOffset;
WriteBarrierStructureID m_inlineAccessBaseStructureID;
};
std::atomic<uint64_t> m_packedSelfWord;
};
JSCell* m_inlineHolder { nullptr };
CacheableIdentifier m_identifier;
CodeLocationLabel<JSInternalPtrTag> doneLocation;
CodeLocationLabel<JITStubRoutinePtrTag> slowPathStartLocation;
// TSAN ic-stubinfo (TSAN-TRIAGE §10.4): written by IC initialization
// (initializeFromUnlinkedPropertyInlineCache /
// initializeFromDFGUnlinkedPropertyInlineCache /
// JITInlineCacheGenerator's finalize) and read lock-free by IC slow
// paths on any thread via globalObject(). ICRacyCell routes the
// constructor init, every writer assignment (including the ones in
// jit/JITInlineCacheGenerator.h, which assign through operator=), and
// the reader through relaxed atomics: identical pointer-mov codegen,
// defined under the cross-thread pairing TSAN reported
// ("initializeFromDFGUnlinkedPropertyInlineCache x globalObject").
// Real publication ordering is the storeStoreFence/install protocol of
// the owning CodeBlock (F1/I5); JIT'd readers via offsetOfGlobalObject
// are unchanged (same size/offset).
ICRacyCell<JSGlobalObject*> m_globalObject { nullptr };
private:
// Handler chain: used by both modes. Handler IC uses this as the main dispatch chain
// (accessed from JIT via offsetOfHandler()). Repatching IC uses it in
// rewireStubAsJumpInAccess() and initializeWithUnitHandler().
RefPtr<InlineCacheHandler> m_handler;
// Represents those structures that already have buffered AccessCases in the PolymorphicAccess.
// Note that it's always safe to clear this. If we clear it prematurely, then if we see the same
// structure again during this buffering countdown, we will create an AccessCase object for it.
// That's not so bad - we'll get rid of the redundant ones once we regenerate.
Variant<std::monostate, Vector<StructureID>, Vector<std::tuple<StructureID, CacheableIdentifier>>> m_bufferedStructures WTF_GUARDED_BY_LOCK(m_bufferedStructuresLock);
public:
ScalarRegisterSet usedRegisters;
CallSiteIndex callSiteIndex;
// FIXME: These should only be needed by the repatching ICs but it's slightly non-trivial to move them there as different AccessTypes use different pinned registers.
GPRReg m_baseGPR { InvalidGPRReg };
GPRReg m_valueGPR { InvalidGPRReg };
GPRReg m_extraGPR { InvalidGPRReg };
GPRReg m_extra2GPR { InvalidGPRReg };
GPRReg m_propertyCacheGPR { InvalidGPRReg };
GPRReg m_arrayProfileGPR { InvalidGPRReg };
#if USE(JSVALUE32_64)
GPRReg m_valueTagGPR { InvalidGPRReg };
// FIXME: [32-bits] Check if PropertyInlineCache::m_baseTagGPR is used somewhere.
// https://bugs.webkit.org/show_bug.cgi?id=204726
GPRReg m_baseTagGPR { InvalidGPRReg };
GPRReg m_extraTagGPR { InvalidGPRReg };
GPRReg m_extra2TagGPR { InvalidGPRReg };
#endif
AccessType accessType { AccessType::GetById };
protected:
CacheType m_cacheType { CacheType::Unset };
public:
CacheType preconfiguredCacheType { CacheType::Unset };
// We repatch only when this is zero. If not zero, we decrement.
// Setting 1 for a totally clear stub, we'll patch it after the first execution.
//
// TSAN ic-stubinfo (SPEC-jit §5.7.7): these four cool-down bytes are
// advisory profiling state mutated from IC slow paths on any thread with
// no lock held (considerRepatchingCacheImpl); they are relaxed atomics so
// the cross-thread accesses are defined, with lost updates tolerated.
// Same size and offset as the previous plain uint8_t fields
// (offsetOfCountdown unchanged); JIT'd fast-path accesses to countdown
// stay plain by design (§5.7.1 analogue: TSAN does not see JIT'd code,
// and the value race is blessed). ICRacyCell (not WTF::Atomic) so that
// CONSTRUCTION is also a relaxed store — the WTF::Atomic constructor
// initializes with a plain store, which TSAN paired against concurrent
// relaxed readers (TSAN-TRIAGE §10.4 "DataOnly IC ctor publication").
ICRacyCell<uint8_t> countdown { 1 };
ICRacyCell<uint8_t> repatchCount { 0 };
ICRacyCell<uint8_t> numberOfCoolDowns { 0 };
ICRacyCell<uint8_t> bufferingCountdown;
// T4-ic-never-settles-giloff: saturating count of RetryCacheLater results
// that left this IC still at CacheType::Unset. Consulted only under
// vm.gilOff() by the repatch* tails to escalate to the GaveUp slow path
// when a GIL-off-only guard will never clear. Same advisory relaxed-atomic
// discipline as the four cool-down bytes above; placed after them so
// offsetOfCountdown() is unchanged. Zero-initialized → flag-off identical.
ICRacyCell<uint8_t> retryWithoutProgressCount { 0 };
private:
Lock m_bufferedStructuresLock;
public:
// See ICRacyStateBool above: advisory flags, each its own relaxed atomic
// byte (previously one shared bitfield byte whose RMW writes raced with
// every reader of every other bit, m_icType included).
ICRacyStateBool resetByGC { false };
ICRacyStateBool tookSlowPath { false };
ICRacyStateBool everConsidered { false };
ICRacyStateBool prototypeIsKnownObject { false }; // Only relevant for InstanceOf.
ICRacyStateBool sawNonCell { false };
ICRacyStateBool propertyIsString { false };
ICRacyStateBool propertyIsInt32 { false };
ICRacyStateBool propertyIsSymbol { false };
ICRacyStateBool canBeMegamorphic { false };
// No longer a bitfield: this const discriminator used to share its byte
// with the mutable flags above, so every racy flag write also "wrote" the
// m_icType bit, pairing with lock-free isHandlerIC() readers in TSAN.
// It is written once at construction and immutable afterwards.
const PropertyInlineCacheType m_icType;
};
static_assert(sizeof(WTF::Atomic<uint8_t>) == 1);
// SPEC-jit section 4.2 layout proof (Task 4): the repacked unit is exactly one
// aligned 64-bit word and the per-field views sit at their pre-repack offsets
// (offset half at +0, structure-id half at +4 on little-endian), so flag-off
// emission and C++ accesses are unchanged (I1) and the flag-on single 64-bit
// load/store cannot tear the pair (I6/F3).
static_assert(sizeof(PropertyOffset) == 4);
static_assert(sizeof(WriteBarrierStructureID) == 4);
static_assert(sizeof(std::atomic<uint64_t>) == 8);
static_assert(alignof(std::atomic<uint64_t>) == 8);
static_assert(std::atomic<uint64_t>::is_always_lock_free);
static_assert(std::is_trivially_destructible_v<WriteBarrierStructureID>);
static_assert(PropertyInlineCache::offsetOfByIdSelfOffset() == PropertyInlineCache::offsetOfPackedInlineAccessSelfWord());
static_assert(PropertyInlineCache::offsetOfInlineAccessBaseStructureID() == PropertyInlineCache::offsetOfPackedInlineAccessSelfWord() + 4);
static_assert(!(PropertyInlineCache::offsetOfPackedInlineAccessSelfWord() % 8));
// HandlerPropertyInlineCache
// ==========================
// Implements handler-list dispatch. The call site never modifies machine code;
// instead, as new cases are learned, handler nodes are prepended to a linked list
// that the call site walks at runtime.
//
// Call site layout (Baseline / DFG JIT):
//
// load handlerGPR, [PropertyInlineCache + offsetOfHandler] // head of list
// call [handlerGPR + InlineCacheHandler::offsetOfJumpTarget] // enter first handler
//
// Handler chain layout in memory:
//
// PropertyInlineCache
// +------------------+
// | m_handler |---> InlineCacheHandler #N (most recently added, checked first)
// +------------------+ +-------------------+
// | structureID |
// | offset / uid |
// | m_next |---> InlineCacheHandler #N-1
// +-------------------+ +-------------------+
// | ... |
// | m_next |---> (slow-path handler)
// +-------------------+
//
// Most handler stubs follow a uniform pattern compiled by InlineCacheCompiler::compileHandler():
//
// emitDataICPrologue() // x86_64 pushes FP; ARM64E tags return address;
// // other ISAs do nothing. callFrameRegister is NOT updated.
// check guard // e.g. do a structure check: load from base, compare against
// // [handlerGPR + offsetOfStructureID]
// --- on match ---
// perform access // load / store, depending on AccessCase kind
// emitDataICEpilogue() // minimal inverse of prologue
// return
//
// --- on match (JS/C++ call needed, e.g. getter/setter/custom accessor) ---
// emitDataICPrepareForCall() // lazily save LR/FP now that we know we need a full frame
// call into JS/C++
// emitDataICRestoreAfterCall() // restore LR/FP
// emitDataICEpilogue()
// return
//
// --- on miss ---
// load handlerGPR, [handlerGPR + offsetOfNext]
// jump [handlerGPR + offsetOfJumpTarget] // offset skips prologue
//
// Not modifying callFrameRegister is important because it means handler stubs execute in the caller's frame
// context, so exception unwinding and CallFrame* access via callFrameRegister need no special
// handling in the handler prologue/epilogue. The lazy save/restore pattern also means that
// simple load/store handlers (the common case) pay no frame-setup cost at all.
//
// The terminal handler is always the slow-path. It calls m_slowOperation
// to fall back to the C++ runtime. The slow path may generate and prepend a new
// InlineCacheHandler to the front of the list (LIFO ordering).
//
// Cached-field fast path (m_inlinedHandler):
//
// For simple access patterns (GetByIdSelf, PutByIdReplace, InByIdSelf, etc.) that match
// preconfiguredCacheType, we also store the handler in m_inlinedHandler and write the
// structure, offset, and holder into the IC's own fields (m_inlineAccessBaseStructureID,
// byIdSelfOffset, m_inlineHolder). The JIT emits a structure check inline at the call
// site (before loading m_handler) that can succeed without touching the chain at all.
// These fields are read from memory at runtime, so no constants are embedded in the
// generated assembly.
//
// Watchpoint invalidation:
//
// Some access cases (e.g. loading a property from a prototype assumed not to change)
// attach a PropertyInlineCacheClearingWatchpoint to the relevant WatchpointSet. When
// the watchpoint fires, PropertyInlineCacheClearingWatchpoint::fireInternal() eventually calls
// resetStubAsJumpInAccess(). That function walks the entire m_handler chain calling
// removeOwner() on each node, then assigns m_handler to a freshly generated slow-path
// handler, dropping the old chain's RefPtr and potentially running every
// InlineCacheHandler destructor in the chain.
//
// N.B. A watchpoint can fire while a handler stub is mid-execution — for example, a
// getter or custom accessor calls JS, that JS mutates a prototype, and the watchpoint
// fires before the getter returns. We rely on two distinct practices to avoid
// use-after-free:
//
// 1. InlineCacheHandler struct: if the chain is reset while a stub is on the
// stack and no other Ref holds the node, the InlineCacheHandler wrapper and its
// trailing DataOnlyCallLinkInfo array are freed immediately. This is safe because
// handler stubs access InlineCacheHandler fields only before making calls: the
// structure check and the m_next load both occur on the miss path, ahead of any
// JS call. After the call returns, the result is in a register and the stub
// performs the epilogue and returns without touching the handler struct again.
//
// 2. Machine code: each InlineCacheHandler holds a
// Ref<GCAwareJITStubRoutine>. GCAwareJITStubRoutine does not free the routine
// immediately when its refcount reaches zero; it sets m_isJettisoned = true and
// defers actual deletion until the GC confirms that the routine is no longer on
// any call stack. So the code being executed remains valid even if m_handler is
// cleared under us.
//
// Shared handler thunks:
//
// Many common handler shapes (e.g. getByIdLoadOwnPropertyHandler, putByIdReplaceHandler)
// are pre-compiled as shared thunks stored in VM::m_sharedJITStubs. compileHandler()
// reuses an existing shared stub before generating a new one, so multiple IC sites with
// the same access pattern share the same machine code. This sharing works because
// everything is data only, unlike with repatching ICs.
class HandlerPropertyInlineCache final : public PropertyInlineCache {
WTF_MAKE_NONCOPYABLE(HandlerPropertyInlineCache);
public:
HandlerPropertyInlineCache()
: PropertyInlineCache(PropertyInlineCacheType::Handler)
{ }
HandlerPropertyInlineCache(AccessType accessType, CodeOrigin codeOrigin)
: PropertyInlineCache(PropertyInlineCacheType::Handler, accessType, codeOrigin)
{ }
void initializeFromUnlinkedPropertyInlineCache(VM&, CodeBlock*, const BaselineUnlinkedPropertyInlineCache&);
void initializeFromDFGUnlinkedPropertyInlineCache(CodeBlock*, const DFG::UnlinkedPropertyInlineCache&);
// FTL handler-IC sites (useHandlerICInFTL): the generator recorded
// identifier/registers/locations at compile and link time; this installs the
// shared slow-path handler as the initial chain head. Main thread only, and
// must run before the owning code is installed (FTL::JITFinalizer::finalize).
void initializeHandlerForOptimizingJIT(CodeBlock*);
void setInlinedHandler(CodeBlock*, Ref<InlineCacheHandler>&&);
void clearInlinedHandler(CodeBlock*);
static constexpr ptrdiff_t offsetOfSlowOperation() { return OBJECT_OFFSETOF(HandlerPropertyInlineCache, m_slowOperation); }
CodePtr<OperationPtrTag> m_slowOperation;
RefPtr<InlineCacheHandler> m_inlinedHandler;
};
// RepatchingPropertyInlineCache
// =============================
// Implements slab-patching dispatch (used by FTL only). The call site owns a
// fixed-size region of inline machine code embedded in the compiled function body.
// When new cases are learned, we rewrite either that slab in place, or a
// separately-allocated stub it jumps to.
//
// Inline code region layout inside the JIT-compiled function body:
//
// startLocation --> +--------------------------------------+
// | inline IC code (inlineCodeSize |
// | bytes; initially: call to slow path)|
// +--------------------------------------+
// doneLocation --> (next instruction in the function)
//
// The IC evolves through the following states:
//
// 1. [slow path]: initial state; the slab contains a call to operationXyzOptimize.
//
// 2. [inline access]: after the first hit. InlineAccess patches the slab in-place
// with a monomorphic structure check and access (e.g. a direct load at a known
// offset). No separate stub is needed yet.
//
// 3. [jump -> stub]: when a second structure is seen, rewireStubAsJumpInAccess()
// overwrites the slab with a direct jump to a PolymorphicAccess stub. The stub
// holds all accumulated AccessCases compiled together by InlineCacheCompiler::compile().
//
// The stub uses one of two dispatch strategies, chosen when the stub is (re)generated:
//
// Cascade (linear, newest-first):
// case N: check guard --match--> perform access, jump to doneLocation
// --miss --> fall through
// case N-1: check guard --match--> perform access, jump to doneLocation
// --miss --> fall through
// ...
// slow path
//
// BinarySwitch (O(log n)): used when every case is guarded solely by a structure
// check (no proxies, no non-structure guards). A balanced binary tree of
// structureID comparisons is emitted; each leaf performs its access and jumps
// to doneLocation. We cannot use this form if any case involves a proxy, since
// proxies require additional checks beyond the structureID.
//
// Because compiling a new stub is expensive, new cases are buffered in the
// PolymorphicAccess case list and the stub is only regenerated when bufferingCountdown
// reaches zero (reset to Options::repatchBufferingCountdown() after each regeneration).
// This batches multiple new cases into a single regeneration pass.
//
// Why only in FTL:
//
// Rewriting machine code at runtime incurs instruction cache flushes and, on some
// platforms, requires toggling memory write permissions. The generated stub code is
// also bespoke (built for exactly the set of cases we have seen), so every new case
// requires a full recompile of the entire stub. The payoff is tight dispatch: no
// indirect branches through a handler chain, and potentially O(log n) dispatch via
// binary switch. This tradeoff is only worthwhile in the FTL, where a function is
// hot enough to amortize the patching cost over many executions.
//
// Watchpoint invalidation:
//
// Cases that depend on stable conditions (prototype-chain stability, equivalence of a
// property value, etc.) register watchpoints on the relevant WatchpointSets. When a
// watchpoint fires, PropertyInlineCacheClearingWatchpoint::fireInternal() eventually calls
// resetStubAsJumpInAccess(). For RepatchingIC that function overwrites the inline slab
// with a jump back to the slow path and drops the reference to the PolymorphicAccess
// stub; the IC then begins accumulating cases from scratch.
//
// If a stub is mid-execution when the watchpoint fires (e.g. a polymorphic accessor
// case calls JS), the machine code is protected by GCAwareJITStubRoutine (as described above),
// this guarantees the code of the stub is still valid. No inline slabs can be at the
// first instruction when this rewrite happens either.
class RepatchingPropertyInlineCache final : public PropertyInlineCache {
WTF_MAKE_NONCOPYABLE(RepatchingPropertyInlineCache);
public:
RepatchingPropertyInlineCache()
: PropertyInlineCache(PropertyInlineCacheType::Repatching)
{
// SPEC-jit I3: with shared-memory threads enabled, every property IC is
// a handler IC (pure data dispatch); repatching ICs would patch machine
// code in place under concurrent execution (sections 5.2/5.3).
RELEASE_ASSERT(!Options::useJSThreads());
}
RepatchingPropertyInlineCache(AccessType accessType, CodeOrigin codeOrigin)
: PropertyInlineCache(PropertyInlineCacheType::Repatching, accessType, codeOrigin)
{
// SPEC-jit I3: see above.
RELEASE_ASSERT(!Options::useJSThreads());
}