There is no good reason for WeakBlock to care about newly allocated objects
[WebKit-https.git] / Source / JavaScriptCore / heap / MarkedBlock.h
1 /*
2  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
3  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
4  *  Copyright (C) 2003-2009, 2011, 2016 Apple Inc. All rights reserved.
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifndef MarkedBlock_h
23 #define MarkedBlock_h
24
25 #include "AllocatorAttributes.h"
26 #include "DestructionMode.h"
27 #include "FreeList.h"
28 #include "HeapCell.h"
29 #include "HeapOperation.h"
30 #include "IterationStatus.h"
31 #include "WeakSet.h"
32 #include <wtf/Bitmap.h>
33 #include <wtf/DataLog.h>
34 #include <wtf/DoublyLinkedList.h>
35 #include <wtf/HashFunctions.h>
36 #include <wtf/StdLibExtras.h>
37
38 namespace JSC {
39     
40 class Heap;
41 class JSCell;
42 class MarkedAllocator;
43
44 typedef uintptr_t Bits;
45 typedef uint32_t HeapVersion;
46
47 // Set to log state transitions of blocks.
48 #define HEAP_LOG_BLOCK_STATE_TRANSITIONS 0
49
50 #if HEAP_LOG_BLOCK_STATE_TRANSITIONS
51 #define HEAP_LOG_BLOCK_STATE_TRANSITION(handle) do {            \
52         dataLogF(                                               \
53             "%s:%d %s: block %s = %p, %d\n",                    \
54             __FILE__, __LINE__, __FUNCTION__,                   \
55             #handle, &(handle)->block(), (handle)->m_state);    \
56     } while (false)
57 #else
58 #define HEAP_LOG_BLOCK_STATE_TRANSITION(handle) ((void)0)
59 #endif
60
61 // A marked block is a page-aligned container for heap-allocated objects.
62 // Objects are allocated within cells of the marked block. For a given
63 // marked block, all cells have the same size. Objects smaller than the
64 // cell size may be allocated in the marked block, in which case the
65 // allocation suffers from internal fragmentation: wasted space whose
66 // size is equal to the difference between the cell size and the object
67 // size.
68
69 class MarkedBlock {
70     WTF_MAKE_NONCOPYABLE(MarkedBlock);
71     friend class LLIntOffsetsExtractor;
72     friend struct VerifyMarked;
73
74 public:
75     class Handle;
76 private:
77     friend class Handle;
78 public:
79     enum BlockState : uint8_t { New, FreeListed, Allocated, Marked };
80         
81     static const size_t atomSize = 16; // bytes
82     static const size_t blockSize = 16 * KB;
83     static const size_t blockMask = ~(blockSize - 1); // blockSize must be a power of two.
84
85     static const size_t atomsPerBlock = blockSize / atomSize;
86
87     static_assert(!(MarkedBlock::atomSize & (MarkedBlock::atomSize - 1)), "MarkedBlock::atomSize must be a power of two.");
88     static_assert(!(MarkedBlock::blockSize & (MarkedBlock::blockSize - 1)), "MarkedBlock::blockSize must be a power of two.");
89
90     struct VoidFunctor {
91         typedef void ReturnType;
92         void returnValue() { }
93     };
94
95     class CountFunctor {
96     public:
97         typedef size_t ReturnType;
98
99         CountFunctor() : m_count(0) { }
100         void count(size_t count) const { m_count += count; }
101         ReturnType returnValue() const { return m_count; }
102
103     private:
104         // FIXME: This is mutable because we're using a functor rather than C++ lambdas.
105         // https://bugs.webkit.org/show_bug.cgi?id=159644
106         mutable ReturnType m_count;
107     };
108         
109     class Handle : public BasicRawSentinelNode<Handle> {
110         WTF_MAKE_NONCOPYABLE(Handle);
111         WTF_MAKE_FAST_ALLOCATED;
112         friend class DoublyLinkedListNode<Handle>;
113         friend class LLIntOffsetsExtractor;
114         friend class MarkedBlock;
115         friend struct VerifyMarked;
116     public:
117             
118         ~Handle();
119             
120         MarkedBlock& block();
121             
122         void* cellAlign(void*);
123             
124         bool isEmpty();
125
126         void lastChanceToFinalize();
127
128         MarkedAllocator* allocator() const;
129         Heap* heap() const;
130         VM* vm() const;
131         WeakSet& weakSet();
132             
133         enum SweepMode { SweepOnly, SweepToFreeList };
134         FreeList sweep(SweepMode = SweepOnly);
135         
136         void unsweepWithNoNewlyAllocated();
137         
138         void zap(const FreeList&);
139         
140         void shrink();
141             
142         unsigned visitWeakSet(HeapRootVisitor&);
143         void reapWeakSet();
144             
145         // While allocating from a free list, MarkedBlock temporarily has bogus
146         // cell liveness data. To restore accurate cell liveness data, call one
147         // of these functions:
148         void didConsumeFreeList(); // Call this once you've allocated all the items in the free list.
149         void stopAllocating(const FreeList&);
150         FreeList resumeAllocating(); // Call this if you canonicalized a block for some non-collection related purpose.
151             
152         // Returns true if the "newly allocated" bitmap was non-null 
153         // and was successfully cleared and false otherwise.
154         bool clearNewlyAllocated();
155             
156         void flipForEdenCollection();
157             
158         size_t cellSize();
159         const AllocatorAttributes& attributes() const;
160         DestructionMode destruction() const;
161         bool needsDestruction() const;
162         HeapCell::Kind cellKind() const;
163             
164         size_t markCount();
165         size_t size();
166             
167         bool isLive(const HeapCell*);
168         bool isLiveCell(const void*);
169         bool isMarkedOrNewlyAllocated(const HeapCell*);
170             
171         bool isNewlyAllocated(const void*);
172         void setNewlyAllocated(const void*);
173         void clearNewlyAllocated(const void*);
174         
175         bool hasAnyNewlyAllocated() const { return !!m_newlyAllocated; }
176             
177         bool isAllocated() const;
178         bool isMarked() const;
179         bool isFreeListed() const;
180         bool needsSweeping() const;
181         void willRemoveBlock();
182
183         template <typename Functor> IterationStatus forEachCell(const Functor&);
184         template <typename Functor> IterationStatus forEachLiveCell(const Functor&);
185         template <typename Functor> IterationStatus forEachDeadCell(const Functor&);
186             
187         bool needsFlip();
188             
189         void flipIfNecessaryDuringMarking(HeapVersion);
190         void flipIfNecessary(HeapVersion);
191         void flipIfNecessary();
192             
193         void assertFlipped();
194             
195         bool isOnBlocksToSweep() const { return m_isOnBlocksToSweep; }
196         void setIsOnBlocksToSweep(bool value) { m_isOnBlocksToSweep = value; }
197         
198         BlockState state() const { return m_state; }
199             
200     private:
201         Handle(Heap&, MarkedAllocator*, size_t cellSize, const AllocatorAttributes&, void*);
202             
203         template<DestructionMode>
204         FreeList sweepHelperSelectScribbleMode(SweepMode = SweepOnly);
205             
206         enum ScribbleMode { DontScribble, Scribble };
207             
208         template<DestructionMode, ScribbleMode>
209         FreeList sweepHelperSelectStateAndSweepMode(SweepMode = SweepOnly);
210             
211         enum NewlyAllocatedMode { HasNewlyAllocated, DoesNotHaveNewlyAllocated };
212             
213         template<BlockState, SweepMode, DestructionMode, ScribbleMode, NewlyAllocatedMode>
214         FreeList specializedSweep();
215             
216         template<typename Func>
217         void forEachFreeCell(const FreeList&, const Func&);
218             
219         MarkedBlock::Handle* m_prev;
220         MarkedBlock::Handle* m_next;
221             
222         size_t m_atomsPerCell;
223         size_t m_endAtom; // This is a fuzzy end. Always test for < m_endAtom.
224             
225         std::unique_ptr<WTF::Bitmap<atomsPerBlock>> m_newlyAllocated;
226             
227         AllocatorAttributes m_attributes;
228         BlockState m_state;
229         bool m_isOnBlocksToSweep { false };
230             
231         MarkedAllocator* m_allocator;
232         WeakSet m_weakSet;
233             
234         MarkedBlock* m_block;
235     };
236         
237     static MarkedBlock::Handle* tryCreate(Heap&, MarkedAllocator*, size_t cellSize, const AllocatorAttributes&);
238         
239     Handle& handle();
240         
241     VM* vm() const;
242
243     static bool isAtomAligned(const void*);
244     static MarkedBlock* blockFor(const void*);
245     static size_t firstAtom();
246     size_t atomNumber(const void*);
247         
248     size_t markCount();
249
250     bool isMarked(const void*);
251     bool testAndSetMarked(const void*);
252         
253     bool isMarkedOrNewlyAllocated(const HeapCell*);
254     
255     bool isMarkedDuringWeakVisiting(HeapVersion, const HeapCell*);
256
257     bool isAtom(const void*);
258     void clearMarked(const void*);
259         
260     size_t cellSize();
261     const AllocatorAttributes& attributes() const;
262
263     bool hasAnyMarked() const;
264     void noteMarked();
265         
266     WeakSet& weakSet();
267
268     bool needsFlip(HeapVersion);
269     bool needsFlip();
270         
271     void flipIfNecessaryDuringMarking(HeapVersion);
272     void flipIfNecessary(HeapVersion);
273     void flipIfNecessary();
274         
275     void assertFlipped();
276         
277     bool needsDestruction() const { return m_needsDestruction; }
278         
279 private:
280     static const size_t atomAlignmentMask = atomSize - 1;
281
282     typedef char Atom[atomSize];
283
284     MarkedBlock(VM&, Handle&);
285     Atom* atoms();
286         
287     void flipIfNecessaryDuringMarkingSlow();
288     void flipIfNecessarySlow();
289     void clearMarks();
290     void clearHasAnyMarked();
291     
292     void noteMarkedSlow();
293         
294     WTF::Bitmap<atomsPerBlock, WTF::BitmapAtomic, uint8_t> m_marks;
295
296     bool m_needsDestruction;
297     Lock m_lock;
298     
299     // The actual mark count can be computed by doing: m_biasedMarkCount - m_markCountBias. Note
300     // that this count is racy. It will accurately detect whether or not exactly zero things were
301     // marked, but if N things got marked, then this may report anything in the range [1, N] (or
302     // before unbiased, it would be [1 + m_markCountBias, N + m_markCountBias].)
303     int16_t m_biasedMarkCount;
304     
305     // We bias the mark count so that if m_biasedMarkCount >= 0 then the block should be retired.
306     // We go to all this trouble to make marking a bit faster: this way, marking knows when to
307     // retire a block using a js/jns on m_biasedMarkCount.
308     //
309     // For example, if a block has room for 100 objects and retirement happens whenever 90% are
310     // live, then m_markCountBias will be -90. This way, when marking begins, this will cause us to
311     // set m_biasedMarkCount to -90 as well, since:
312     //
313     //     m_biasedMarkCount = actualMarkCount + m_markCountBias.
314     //
315     // Marking an object will increment m_biasedMarkCount. Once 90 objects get marked, we will have
316     // m_biasedMarkCount = 0, which will trigger retirement. In other words, we want to set
317     // m_markCountBias like so:
318     //
319     //     m_markCountBias = -(minMarkedBlockUtilization * cellsPerBlock)
320     //
321     // All of this also means that you can detect if any objects are marked by doing:
322     //
323     //     m_biasedMarkCount != m_markCountBias
324     int16_t m_markCountBias;
325
326     HeapVersion m_version;
327     
328     Handle& m_handle;
329     VM* m_vm;
330 };
331
332 inline MarkedBlock::Handle& MarkedBlock::handle()
333 {
334     return m_handle;
335 }
336
337 inline MarkedBlock& MarkedBlock::Handle::block()
338 {
339     return *m_block;
340 }
341
342 inline size_t MarkedBlock::firstAtom()
343 {
344     return WTF::roundUpToMultipleOf<atomSize>(sizeof(MarkedBlock)) / atomSize;
345 }
346
347 inline MarkedBlock::Atom* MarkedBlock::atoms()
348 {
349     return reinterpret_cast<Atom*>(this);
350 }
351
352 inline bool MarkedBlock::isAtomAligned(const void* p)
353 {
354     return !(reinterpret_cast<Bits>(p) & atomAlignmentMask);
355 }
356
357 inline void* MarkedBlock::Handle::cellAlign(void* p)
358 {
359     Bits base = reinterpret_cast<Bits>(block().atoms() + firstAtom());
360     Bits bits = reinterpret_cast<Bits>(p);
361     bits -= base;
362     bits -= bits % cellSize();
363     bits += base;
364     return reinterpret_cast<void*>(bits);
365 }
366
367 inline MarkedBlock* MarkedBlock::blockFor(const void* p)
368 {
369     return reinterpret_cast<MarkedBlock*>(reinterpret_cast<Bits>(p) & blockMask);
370 }
371
372 inline MarkedAllocator* MarkedBlock::Handle::allocator() const
373 {
374     return m_allocator;
375 }
376
377 inline Heap* MarkedBlock::Handle::heap() const
378 {
379     return m_weakSet.heap();
380 }
381
382 inline VM* MarkedBlock::Handle::vm() const
383 {
384     return m_weakSet.vm();
385 }
386
387 inline VM* MarkedBlock::vm() const
388 {
389     return m_vm;
390 }
391
392 inline WeakSet& MarkedBlock::Handle::weakSet()
393 {
394     return m_weakSet;
395 }
396
397 inline WeakSet& MarkedBlock::weakSet()
398 {
399     return m_handle.weakSet();
400 }
401
402 inline void MarkedBlock::Handle::shrink()
403 {
404     m_weakSet.shrink();
405 }
406
407 inline unsigned MarkedBlock::Handle::visitWeakSet(HeapRootVisitor& heapRootVisitor)
408 {
409     return m_weakSet.visit(heapRootVisitor);
410 }
411
412 inline void MarkedBlock::Handle::reapWeakSet()
413 {
414     m_weakSet.reap();
415 }
416
417 inline size_t MarkedBlock::Handle::cellSize()
418 {
419     return m_atomsPerCell * atomSize;
420 }
421
422 inline size_t MarkedBlock::cellSize()
423 {
424     return m_handle.cellSize();
425 }
426
427 inline const AllocatorAttributes& MarkedBlock::Handle::attributes() const
428 {
429     return m_attributes;
430 }
431
432 inline const AllocatorAttributes& MarkedBlock::attributes() const
433 {
434     return m_handle.attributes();
435 }
436
437 inline bool MarkedBlock::Handle::needsDestruction() const
438 {
439     return m_attributes.destruction == NeedsDestruction;
440 }
441
442 inline DestructionMode MarkedBlock::Handle::destruction() const
443 {
444     return m_attributes.destruction;
445 }
446
447 inline HeapCell::Kind MarkedBlock::Handle::cellKind() const
448 {
449     return m_attributes.cellKind;
450 }
451
452 inline size_t MarkedBlock::Handle::markCount()
453 {
454     return m_block->markCount();
455 }
456
457 inline size_t MarkedBlock::Handle::size()
458 {
459     return markCount() * cellSize();
460 }
461
462 inline size_t MarkedBlock::atomNumber(const void* p)
463 {
464     return (reinterpret_cast<Bits>(p) - reinterpret_cast<Bits>(this)) / atomSize;
465 }
466
467 inline bool MarkedBlock::needsFlip(HeapVersion heapVersion)
468 {
469     return heapVersion != m_version;
470 }
471
472 inline void MarkedBlock::flipIfNecessary(HeapVersion heapVersion)
473 {
474     if (UNLIKELY(needsFlip(heapVersion)))
475         flipIfNecessarySlow();
476 }
477
478 inline void MarkedBlock::flipIfNecessaryDuringMarking(HeapVersion heapVersion)
479 {
480     if (UNLIKELY(needsFlip(heapVersion)))
481         flipIfNecessaryDuringMarkingSlow();
482     WTF::loadLoadFence();
483 }
484
485 inline void MarkedBlock::Handle::flipIfNecessary(HeapVersion heapVersion)
486 {
487     block().flipIfNecessary(heapVersion);
488 }
489
490 inline void MarkedBlock::Handle::flipIfNecessaryDuringMarking(HeapVersion heapVersion)
491 {
492     block().flipIfNecessaryDuringMarking(heapVersion);
493 }
494
495 inline void MarkedBlock::Handle::flipForEdenCollection()
496 {
497     assertFlipped();
498         
499     HEAP_LOG_BLOCK_STATE_TRANSITION(this);
500     
501     ASSERT(m_state != New && m_state != FreeListed);
502     
503     m_state = Marked;
504 }
505
506 #if ASSERT_DISABLED
507 inline void MarkedBlock::assertFlipped()
508 {
509 }
510 #endif // ASSERT_DISABLED
511
512 inline void MarkedBlock::Handle::assertFlipped()
513 {
514     block().assertFlipped();
515 }
516
517 inline bool MarkedBlock::isMarked(const void* p)
518 {
519     assertFlipped();
520     return m_marks.get(atomNumber(p));
521 }
522
523 inline bool MarkedBlock::testAndSetMarked(const void* p)
524 {
525     assertFlipped();
526     return m_marks.concurrentTestAndSet(atomNumber(p));
527 }
528
529 inline bool MarkedBlock::Handle::isNewlyAllocated(const void* p)
530 {
531     return m_newlyAllocated->get(m_block->atomNumber(p));
532 }
533
534 inline void MarkedBlock::Handle::setNewlyAllocated(const void* p)
535 {
536     m_newlyAllocated->set(m_block->atomNumber(p));
537 }
538
539 inline void MarkedBlock::Handle::clearNewlyAllocated(const void* p)
540 {
541     m_newlyAllocated->clear(m_block->atomNumber(p));
542 }
543
544 inline bool MarkedBlock::Handle::clearNewlyAllocated()
545 {
546     if (m_newlyAllocated) {
547         m_newlyAllocated = nullptr;
548         return true;
549     }
550     return false;
551 }
552
553 inline bool MarkedBlock::Handle::isMarkedOrNewlyAllocated(const HeapCell* cell)
554 {
555     ASSERT(m_state == Marked);
556     return m_block->isMarked(cell) || (m_newlyAllocated && isNewlyAllocated(cell));
557 }
558
559 inline bool MarkedBlock::isMarkedOrNewlyAllocated(const HeapCell* cell)
560 {
561     ASSERT(m_handle.m_state == Marked);
562     return isMarked(cell) || (m_handle.m_newlyAllocated && m_handle.isNewlyAllocated(cell));
563 }
564
565 inline bool MarkedBlock::isMarkedDuringWeakVisiting(HeapVersion heapVersion, const HeapCell* cell)
566 {
567     if (needsFlip(heapVersion))
568         return false;
569     return isMarked(cell);
570 }
571
572 inline bool MarkedBlock::Handle::isLive(const HeapCell* cell)
573 {
574     assertFlipped();
575     switch (m_state) {
576     case Allocated:
577         return true;
578
579     case Marked:
580         return isMarkedOrNewlyAllocated(cell);
581
582     case New:
583     case FreeListed:
584         RELEASE_ASSERT_NOT_REACHED();
585         return false;
586     }
587
588     RELEASE_ASSERT_NOT_REACHED();
589     return false;
590 }
591
592 inline bool MarkedBlock::isAtom(const void* p)
593 {
594     ASSERT(MarkedBlock::isAtomAligned(p));
595     size_t atomNumber = this->atomNumber(p);
596     size_t firstAtom = MarkedBlock::firstAtom();
597     if (atomNumber < firstAtom) // Filters pointers into MarkedBlock metadata.
598         return false;
599     if ((atomNumber - firstAtom) % m_handle.m_atomsPerCell) // Filters pointers into cell middles.
600         return false;
601     if (atomNumber >= m_handle.m_endAtom) // Filters pointers into invalid cells out of the range.
602         return false;
603     return true;
604 }
605
606 inline bool MarkedBlock::Handle::isLiveCell(const void* p)
607 {
608     if (!m_block->isAtom(p))
609         return false;
610     return isLive(static_cast<const HeapCell*>(p));
611 }
612
613 template <typename Functor>
614 inline IterationStatus MarkedBlock::Handle::forEachCell(const Functor& functor)
615 {
616     HeapCell::Kind kind = m_attributes.cellKind;
617     for (size_t i = firstAtom(); i < m_endAtom; i += m_atomsPerCell) {
618         HeapCell* cell = reinterpret_cast_ptr<HeapCell*>(&m_block->atoms()[i]);
619         if (functor(cell, kind) == IterationStatus::Done)
620             return IterationStatus::Done;
621     }
622     return IterationStatus::Continue;
623 }
624
625 template <typename Functor>
626 inline IterationStatus MarkedBlock::Handle::forEachLiveCell(const Functor& functor)
627 {
628     flipIfNecessary();
629     HeapCell::Kind kind = m_attributes.cellKind;
630     for (size_t i = firstAtom(); i < m_endAtom; i += m_atomsPerCell) {
631         HeapCell* cell = reinterpret_cast_ptr<HeapCell*>(&m_block->atoms()[i]);
632         if (!isLive(cell))
633             continue;
634
635         if (functor(cell, kind) == IterationStatus::Done)
636             return IterationStatus::Done;
637     }
638     return IterationStatus::Continue;
639 }
640
641 template <typename Functor>
642 inline IterationStatus MarkedBlock::Handle::forEachDeadCell(const Functor& functor)
643 {
644     flipIfNecessary();
645     HeapCell::Kind kind = m_attributes.cellKind;
646     for (size_t i = firstAtom(); i < m_endAtom; i += m_atomsPerCell) {
647         HeapCell* cell = reinterpret_cast_ptr<HeapCell*>(&m_block->atoms()[i]);
648         if (isLive(cell))
649             continue;
650
651         if (functor(cell, kind) == IterationStatus::Done)
652             return IterationStatus::Done;
653     }
654     return IterationStatus::Continue;
655 }
656
657 inline bool MarkedBlock::Handle::needsSweeping() const
658 {
659     const_cast<MarkedBlock::Handle*>(this)->flipIfNecessary();
660     return m_state == Marked;
661 }
662
663 inline bool MarkedBlock::Handle::isAllocated() const
664 {
665     const_cast<MarkedBlock::Handle*>(this)->flipIfNecessary();
666     return m_state == Allocated;
667 }
668
669 inline bool MarkedBlock::Handle::isMarked() const
670 {
671     const_cast<MarkedBlock::Handle*>(this)->flipIfNecessary();
672     return m_state == Marked;
673 }
674
675 inline bool MarkedBlock::Handle::isFreeListed() const
676 {
677     const_cast<MarkedBlock::Handle*>(this)->flipIfNecessary();
678     return m_state == FreeListed;
679 }
680
681 inline bool MarkedBlock::hasAnyMarked() const
682 {
683     return m_biasedMarkCount != m_markCountBias;
684 }
685
686 inline void MarkedBlock::noteMarked()
687 {
688     // This is racy by design. We don't want to pay the price of an atomic increment!
689     int16_t biasedMarkCount = m_biasedMarkCount;
690     ++biasedMarkCount;
691     m_biasedMarkCount = biasedMarkCount;
692     if (UNLIKELY(!biasedMarkCount))
693         noteMarkedSlow();
694 }
695
696 } // namespace JSC
697
698 namespace WTF {
699
700 struct MarkedBlockHash : PtrHash<JSC::MarkedBlock*> {
701     static unsigned hash(JSC::MarkedBlock* const& key)
702     {
703         // Aligned VM regions tend to be monotonically increasing integers,
704         // which is a great hash function, but we have to remove the low bits,
705         // since they're always zero, which is a terrible hash function!
706         return reinterpret_cast<JSC::Bits>(key) / JSC::MarkedBlock::blockSize;
707     }
708 };
709
710 template<> struct DefaultHash<JSC::MarkedBlock*> {
711     typedef MarkedBlockHash Hash;
712 };
713
714 void printInternal(PrintStream& out, JSC::MarkedBlock::BlockState);
715
716 } // namespace WTF
717
718 #endif // MarkedBlock_h