GCMarker.h (21285B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef gc_GCMarker_h 8 #define gc_GCMarker_h 9 10 #include "mozilla/Variant.h" 11 #include "mozilla/XorShift128PlusRNG.h" 12 13 #include "gc/Barrier.h" 14 #include "js/HashTable.h" 15 #include "js/TracingAPI.h" 16 #include "js/TypeDecls.h" 17 #include "threading/ProtectedData.h" 18 19 class JSRope; 20 21 namespace JS { 22 class SliceBudget; 23 } 24 25 namespace js { 26 27 class GCMarker; 28 class WeakMapBase; 29 30 #ifdef DEBUG 31 // Force stack resizing to ensure OOM test coverage in debug builds. 32 static const size_t MARK_STACK_BASE_CAPACITY = 4; 33 #else 34 static const size_t MARK_STACK_BASE_CAPACITY = 4096; 35 #endif 36 37 enum class SlotsOrElementsKind { 38 Unused = 0, // Must match SlotsOrElementsRangeTag 39 Elements, 40 FixedSlots, 41 DynamicSlots 42 }; 43 44 namespace gc { 45 46 enum IncrementalProgress { NotFinished = 0, Finished }; 47 48 class AutoSetMarkColor; 49 class AutoUpdateMarkStackRanges; 50 struct Cell; 51 class MarkStackIter; 52 class ParallelMarkTask; 53 class UnmarkGrayTracer; 54 55 // Ephemerons are edges from a source to a target that are only materialized 56 // into a table when the owner is marked. (The owner is something like a 57 // WeakMap, which contains a set of ephemerons each going from a WeakMap key to 58 // its value.) When marking a ephemeron, only the color of the owner is needed: 59 // the target is marked with the minimum (least-marked) color of the owner and 60 // source. So an EphemeronEdge need store only the owner color and the target 61 // pointer, which can fit into a tagged pointer since targets are aligned Cells. 62 // 63 // Note: if the owner's color changes, new EphemeronEdges will be created for 64 // it. 65 class EphemeronEdge { 66 static constexpr uintptr_t ColorMask = 0x3; 67 static_assert(uintptr_t(MarkColor::Gray) <= ColorMask); 68 static_assert(uintptr_t(MarkColor::Black) <= ColorMask); 69 static_assert(ColorMask < CellAlignBytes); 70 71 uintptr_t taggedTarget; 72 73 public: 74 EphemeronEdge(MarkColor color, TenuredCell* cell) 75 : taggedTarget(uintptr_t(cell) | uintptr_t(color)) { 76 MOZ_ASSERT((uintptr_t(cell) & ColorMask) == 0); 77 } 78 79 MarkColor color() const { return MarkColor(taggedTarget & ColorMask); } 80 TenuredCell* target() const { 81 return reinterpret_cast<TenuredCell*>(taggedTarget & ~ColorMask); 82 } 83 }; 84 85 using EphemeronEdgeVector = Vector<EphemeronEdge, 2, js::SystemAllocPolicy>; 86 87 using EphemeronEdgeTable = 88 HashMap<TenuredCell*, EphemeronEdgeVector, PointerHasher<TenuredCell*>, 89 js::SystemAllocPolicy>; 90 91 /* 92 * The mark stack. Pointers in this stack are "gray" in the GC sense, but 93 * their references may be marked either black or gray (in the CC sense). 94 * 95 * When the mark stack is full, the GC does not call js::TraceChildren to mark 96 * the reachable "children" of the thing. Rather the thing is put aside and 97 * js::TraceChildren is called later when the mark stack is empty. 98 * 99 * To implement such delayed marking of the children with minimal overhead for 100 * the normal case of sufficient stack, we link arenas into a list using 101 * Arena::setNextDelayedMarkingArena(). The head of the list is stored in 102 * GCMarker::delayedMarkingList. GCMarker::delayMarkingChildren() adds arenas 103 * to the list as necessary while markAllDelayedChildren() pops the arenas from 104 * the stack until it is empty. 105 */ 106 class MarkStack { 107 public: 108 /* 109 * We use a common mark stack to mark GC things of different types and use 110 * the explicit tags to distinguish them when it cannot be deduced from 111 * the context of push or pop operation. 112 */ 113 enum Tag { 114 SlotsOrElementsRangeTag = 0, // Must match SlotsOrElementsKind::Unused. 115 ObjectTag, 116 SymbolTag, 117 JitCodeTag, 118 ScriptTag, 119 TempRopeTag, 120 121 LastTag = TempRopeTag 122 }; 123 124 static const uintptr_t TagMask = 7; 125 static_assert(TagMask >= uintptr_t(LastTag), 126 "The tag mask must subsume the tags."); 127 static_assert(TagMask <= gc::CellAlignMask, 128 "The tag mask must be embeddable in a Cell*."); 129 130 class TaggedPtr { 131 uintptr_t bits; 132 133 Cell* ptr() const; 134 135 explicit TaggedPtr(uintptr_t bits); 136 137 public: 138 TaggedPtr(Tag tag, Cell* ptr); 139 static TaggedPtr fromBits(uintptr_t bits); 140 141 uintptr_t asBits() const; 142 Tag tag() const; 143 template <typename T> 144 T* as() const; 145 146 JSObject* asRangeObject() const; 147 JSRope* asTempRope() const; 148 149 void assertValid() const; 150 }; 151 152 class SlotsOrElementsRange { 153 uintptr_t startAndKind_; 154 TaggedPtr ptr_; 155 156 static constexpr size_t StartShift = 2; 157 static constexpr size_t KindMask = (1 << StartShift) - 1; 158 159 SlotsOrElementsRange(uintptr_t startAndKind, uintptr_t ptr); 160 161 public: 162 SlotsOrElementsRange(SlotsOrElementsKind kind, JSObject* obj, size_t start); 163 static SlotsOrElementsRange fromBits(uintptr_t startAndKind, uintptr_t ptr); 164 165 void assertValid() const; 166 167 uintptr_t asBits0() const; 168 uintptr_t asBits1() const; 169 170 SlotsOrElementsKind kind() const; 171 size_t start() const; 172 TaggedPtr ptr() const; 173 174 void setStart(size_t newStart); 175 void setEmpty(); 176 }; 177 178 MarkStack(); 179 ~MarkStack(); 180 181 MarkStack(const MarkStack& other) = delete; 182 MarkStack& operator=(const MarkStack& other) = delete; 183 184 void swap(MarkStack& other); 185 186 // The unit for capacity is mark stack words. 187 size_t capacity() const { return capacity_; } 188 #ifdef JS_GC_ZEAL 189 void setMaxCapacity(size_t maxCapacity); 190 #endif 191 192 size_t position() const { return topIndex_; } 193 194 [[nodiscard]] bool init(); 195 [[nodiscard]] bool resetStackCapacity(); 196 197 template <typename T> 198 [[nodiscard]] bool push(T* ptr); 199 void infalliblePush(const SlotsOrElementsRange& range); 200 void infalliblePush(JSObject* obj, SlotsOrElementsKind kind, size_t start); 201 [[nodiscard]] bool push(const TaggedPtr& ptr); 202 void infalliblePush(const TaggedPtr& ptr); 203 204 // GCMarker::eagerlyMarkChildren uses unused marking stack as temporary 205 // storage to hold rope pointers. 206 [[nodiscard]] bool pushTempRope(JSRope* rope); 207 208 bool isEmpty() const { return position() == 0; } 209 bool hasEntries() const { return !isEmpty(); } 210 211 Tag peekTag() const; 212 TaggedPtr popPtr(); 213 SlotsOrElementsRange popSlotsOrElementsRange(); 214 215 void clearAndResetCapacity(); 216 void clearAndFreeStack(); 217 218 void poisonUnused(); 219 220 [[nodiscard]] bool ensureSpace(size_t count); 221 222 static size_t moveWork(GCMarker* marker, MarkStack& dst, MarkStack& src, 223 bool allowDistribute); 224 225 size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; 226 227 private: 228 uintptr_t at(size_t index) const { 229 MOZ_ASSERT(topIndex_ <= capacity_); 230 MOZ_ASSERT(index < topIndex_); 231 return stack_[index]; 232 } 233 uintptr_t* ptr(size_t index) { 234 MOZ_ASSERT(topIndex_ <= capacity_); 235 MOZ_ASSERT(index <= topIndex_); 236 return stack_ + index; 237 } 238 239 // Return a pointer to the first unused word beyond the top of the stack. 240 uintptr_t* end() { return ptr(topIndex_); } 241 242 // Grow the stack, ensuring there is space to push |count| more words. 243 [[nodiscard]] bool enlarge(size_t count); 244 245 [[nodiscard]] bool resize(size_t newCapacity); 246 247 TaggedPtr peekPtr() const; 248 249 [[nodiscard]] bool pushTaggedPtr(Tag tag, Cell* ptr); 250 251 bool indexIsEntryBase(size_t index) const; 252 253 // Area of memory containing the stack. 254 MainThreadOrGCTaskData<uintptr_t*> stack_; 255 256 // Size of the stack in words. 257 MainThreadOrGCTaskData<size_t> capacity_; 258 259 // Index of the top of the stack. 260 MainThreadOrGCTaskData<size_t> topIndex_; 261 262 #ifdef JS_GC_ZEAL 263 // The maximum stack capacity to grow to. 264 MainThreadOrGCTaskData<size_t> maxCapacity_{SIZE_MAX}; 265 #endif 266 267 #ifdef DEBUG 268 MainThreadOrGCTaskData<bool> elementsRangesAreValid; 269 friend class js::GCMarker; 270 #endif 271 272 friend class MarkStackIter; 273 }; 274 275 static_assert(unsigned(SlotsOrElementsKind::Unused) == 276 unsigned(MarkStack::SlotsOrElementsRangeTag), 277 "To split the mark stack we depend on being able to tell the " 278 "difference between SlotsOrElementsRange::startAndKind_ and a " 279 "tagged SlotsOrElementsRange"); 280 281 class MOZ_STACK_CLASS MarkStackIter { 282 MarkStack& stack_; 283 size_t pos_; 284 285 public: 286 explicit MarkStackIter(MarkStack& stack); 287 288 bool done() const; 289 void next(); 290 291 MarkStack::Tag peekTag() const; 292 MarkStack::TaggedPtr peekPtr() const; 293 bool isSlotsOrElementsRange() const; 294 MarkStack::SlotsOrElementsRange slotsOrElementsRange() const; 295 void setSlotsOrElementsRange(const MarkStack::SlotsOrElementsRange& range); 296 297 private: 298 size_t position() const; 299 }; 300 301 // Bitmask of options to parameterize MarkingTracerT. 302 namespace MarkingOptions { 303 enum : uint32_t { 304 None = 0, 305 306 // Set the compartment's hasMarkedCells flag for roots. 307 MarkRootCompartments = 1, 308 309 // The marking tracer is operating in parallel. Use appropriate atomic 310 // accesses to update the mark bits correctly. 311 ParallelMarking = 2, 312 313 // Mark any implicit edges if we are in weak marking mode. 314 MarkImplicitEdges = 4, 315 }; 316 } // namespace MarkingOptions 317 318 // A default set of marking options that works during normal marking and weak 319 // marking modes. Used for barriers and testing code. 320 constexpr uint32_t NormalMarkingOptions = MarkingOptions::MarkImplicitEdges; 321 322 template <uint32_t markingOptions> 323 class MarkingTracerT 324 : public GenericTracerImpl<MarkingTracerT<markingOptions>> { 325 public: 326 MarkingTracerT(JSRuntime* runtime, GCMarker* marker); 327 virtual ~MarkingTracerT() = default; 328 329 template <typename T> 330 void onEdge(T** thingp, const char* name); 331 friend class GenericTracerImpl<MarkingTracerT<markingOptions>>; 332 333 GCMarker* getMarker(); 334 }; 335 336 using MarkingTracer = MarkingTracerT<MarkingOptions::None>; 337 using RootMarkingTracer = MarkingTracerT<MarkingOptions::MarkRootCompartments>; 338 using WeakMarkingTracer = MarkingTracerT<MarkingOptions::MarkImplicitEdges>; 339 using ParallelMarkingTracer = MarkingTracerT<MarkingOptions::ParallelMarking>; 340 341 enum ShouldReportMarkTime : bool { 342 ReportMarkTime = true, 343 DontReportMarkTime = false 344 }; 345 346 } /* namespace gc */ 347 348 class GCMarker { 349 enum MarkingState : uint8_t { 350 // Have not yet started marking. 351 NotActive, 352 353 // Root marking mode. This sets the hasMarkedCells flag on compartments 354 // containing objects and scripts, which is used to make sure we clean up 355 // dead compartments. 356 RootMarking, 357 358 // Main marking mode. Weakmap marking will be populating the 359 // gcEphemeronEdges tables but not consulting them. The state will 360 // transition to WeakMarking until it is done, then back to RegularMarking. 361 RegularMarking, 362 363 // Like RegularMarking but with multiple threads running in parallel. 364 ParallelMarking, 365 366 // Same as RegularMarking except now every marked obj/script is immediately 367 // looked up in the gcEphemeronEdges table to find edges generated by 368 // weakmap keys, and traversing them to their values. Transitions back to 369 // RegularMarking when done. 370 WeakMarking, 371 }; 372 373 public: 374 explicit GCMarker(JSRuntime* rt); 375 [[nodiscard]] bool init(); 376 377 JSRuntime* runtime() { return runtime_; } 378 JSTracer* tracer() { 379 return tracer_.match([](auto& t) -> JSTracer* { return &t; }); 380 } 381 382 #ifdef JS_GC_ZEAL 383 void setMaxCapacity(size_t maxCap) { stack.setMaxCapacity(maxCap); } 384 #endif 385 386 bool isActive() const { return state != NotActive; } 387 bool isRegularMarking() const { return state == RegularMarking; } 388 bool isParallelMarking() const { return state == ParallelMarking; } 389 bool isWeakMarking() const { return state == WeakMarking; } 390 391 gc::MarkColor markColor() const { return markColor_; } 392 393 bool isDrained() const { return stack.isEmpty() && otherStack.isEmpty(); } 394 395 bool hasEntriesForCurrentColor() { return stack.hasEntries(); } 396 bool hasBlackEntries() const { return hasEntries(gc::MarkColor::Black); } 397 bool hasGrayEntries() const { return hasEntries(gc::MarkColor::Gray); } 398 bool hasEntries(gc::MarkColor color) const; 399 400 bool canDonateWork() const; 401 bool shouldDonateWork() const; 402 403 void start(); 404 void stop(); 405 void reset(); 406 407 [[nodiscard]] bool markUntilBudgetExhausted( 408 JS::SliceBudget& budget, 409 gc::ShouldReportMarkTime reportTime = gc::ReportMarkTime); 410 411 void setRootMarkingMode(bool newState); 412 413 bool enterWeakMarkingMode(); 414 void leaveWeakMarkingMode(); 415 416 void enterParallelMarkingMode(); 417 void leaveParallelMarkingMode(); 418 419 // Do not use linear-time weak marking for the rest of this collection. 420 // Currently, this will only be triggered by an OOM when updating needed data 421 // structures. 422 void abortLinearWeakMarking(); 423 424 #ifdef DEBUG 425 // We can't check atom marking if the helper thread lock is already held by 426 // the current thread. This allows us to disable the check. 427 void setCheckAtomMarking(bool check); 428 429 bool shouldCheckCompartments() { return strictCompartmentChecking; } 430 431 bool markOneObjectForTest(JSObject* obj); 432 #endif 433 434 bool markCurrentColorInParallel(gc::ParallelMarkTask* task, 435 JS::SliceBudget& budget); 436 437 template <uint32_t markingOptions, gc::MarkColor> 438 bool markOneColor(JS::SliceBudget& budget); 439 440 static size_t moveWork(GCMarker* dst, GCMarker* src, bool allowDistribute); 441 442 [[nodiscard]] bool initStack(); 443 void resetStackCapacity(); 444 void freeStack(); 445 446 size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; 447 448 static GCMarker* fromTracer(JSTracer* trc) { 449 MOZ_ASSERT(trc->isMarkingTracer()); 450 auto* marker = reinterpret_cast<GCMarker*>(uintptr_t(trc) - 451 offsetof(GCMarker, tracer_)); 452 MOZ_ASSERT(marker->tracer() == trc); 453 return marker; 454 } 455 456 // Internal public methods, for ease of use by the rest of the GC: 457 458 // If |thing| is unmarked, mark it and then traverse its children. 459 template <uint32_t, typename T> 460 void markAndTraverse(T* thing); 461 462 template <typename T> 463 void markImplicitEdges(T* markedThing); 464 465 private: 466 /* 467 * Care must be taken changing the mark color from gray to black. The cycle 468 * collector depends on the invariant that there are no black to gray edges 469 * in the GC heap. This invariant lets the CC not trace through black 470 * objects. If this invariant is violated, the cycle collector may free 471 * objects that are still reachable. 472 */ 473 void setMarkColor(gc::MarkColor newColor); 474 friend class js::gc::AutoSetMarkColor; 475 476 template <typename Tracer> 477 void setMarkingStateAndTracer(MarkingState prev, MarkingState next); 478 479 // The mutator can shift object elements which could invalidate any elements 480 // index on the mark stack. Change the index to be relative to the elements 481 // allocation (to ignore shifted elements) while the mutator is running. 482 void updateRangesAtStartOfSlice(); 483 void updateRangesAtEndOfSlice(); 484 friend class gc::AutoUpdateMarkStackRanges; 485 486 template <uint32_t markingOptions> 487 bool processMarkStackTop(JS::SliceBudget& budget); 488 friend class gc::GCRuntime; 489 490 // Helper methods that coerce their second argument to the base pointer 491 // type. 492 template <uint32_t markingOptions, typename S> 493 void markAndTraverseObjectEdge(S source, JSObject* target) { 494 markAndTraverseEdge<markingOptions>(source, target); 495 } 496 template <uint32_t markingOptions, typename S> 497 void markAndTraverseStringEdge(S source, JSString* target) { 498 markAndTraverseEdge<markingOptions>(source, target); 499 } 500 501 template <uint32_t markingOptions, typename S, typename T> 502 void markAndTraverseEdge(S* source, T* target); 503 template <uint32_t markingOptions, typename S, typename T> 504 void markAndTraverseEdge(S* source, const T& target); 505 506 template <uint32_t markingOptions> 507 bool markAndTraversePrivateGCThing(JSObject* source, gc::Cell* target); 508 509 template <uint32_t markingOptions> 510 bool markAndTraverseSymbol(JSObject* source, JS::Symbol* target); 511 512 template <typename S, typename T> 513 void checkTraversedEdge(S source, T* target); 514 515 // Mark the given GC thing, but do not trace its children. Return true 516 // if the thing became marked. 517 template <uint32_t markingOptions, typename T> 518 [[nodiscard]] bool mark(T* thing); 519 520 // Traverse a GC thing's children, using a strategy depending on the type. 521 // This can either processing them immediately or push them onto the mark 522 // stack for later. 523 #define DEFINE_TRAVERSE_METHOD(_1, Type, _2, _3) \ 524 template <uint32_t> \ 525 void traverse(Type* thing); 526 JS_FOR_EACH_TRACEKIND(DEFINE_TRAVERSE_METHOD) 527 #undef DEFINE_TRAVERSE_METHOD 528 529 // Process a marked thing's children by calling T::traceChildren(). 530 template <uint32_t markingOptions, typename T> 531 void traceChildren(T* thing); 532 533 // Process a marked thing's children recursively using an iterative loop and 534 // manual dispatch, for kinds where this is possible. 535 template <uint32_t markingOptions, typename T> 536 void scanChildren(T* thing); 537 538 // Push a marked thing onto the mark stack. Its children will be marked later. 539 template <uint32_t markingOptions, typename T> 540 void pushThing(T* thing); 541 542 template <uint32_t markingOptions> 543 void eagerlyMarkChildren(JSLinearString* str); 544 template <uint32_t markingOptions> 545 void eagerlyMarkChildren(JSRope* rope); 546 template <uint32_t markingOptions> 547 void eagerlyMarkChildren(JSString* str); 548 template <uint32_t markingOptions> 549 void eagerlyMarkChildren(Shape* shape); 550 template <uint32_t markingOptions> 551 void eagerlyMarkChildren(PropMap* map); 552 template <uint32_t markingOptions> 553 void eagerlyMarkChildren(Scope* scope); 554 555 template <typename T> 556 inline void pushTaggedPtr(T* ptr); 557 558 inline void pushValueRange(JSObject* obj, SlotsOrElementsKind kind, 559 size_t start, size_t end); 560 561 // Mark through edges whose target color depends on the colors of two source 562 // entities (eg a WeakMap and one of its keys), and push the target onto the 563 // mark stack. 564 void markEphemeronEdges(gc::EphemeronEdgeVector& edges, 565 gc::MarkColor srcColor); 566 friend class JS::Zone; 567 568 #ifdef DEBUG 569 void checkZone(gc::Cell* cell); 570 #else 571 void checkZone(gc::Cell* cell) {} 572 #endif 573 574 template <uint32_t markingOptions> 575 bool doMarking(JS::SliceBudget& budget, gc::ShouldReportMarkTime reportTime); 576 577 void delayMarkingChildrenOnOOM(gc::Cell* cell); 578 579 /* 580 * The JSTracer used for marking. This can change depending on the current 581 * state. 582 */ 583 mozilla::Variant<gc::MarkingTracer, gc::RootMarkingTracer, 584 gc::WeakMarkingTracer, gc::ParallelMarkingTracer> 585 tracer_; 586 587 JSRuntime* const runtime_; 588 589 // The main mark stack, holding entries of color |markColor_|. 590 gc::MarkStack stack; 591 592 // The auxiliary mark stack, which may contain entries of the other color. 593 gc::MarkStack otherStack; 594 595 // Track whether we're using the main or auxiliary stack. 596 MainThreadOrGCTaskData<bool> haveSwappedStacks; 597 598 // The current mark stack color. 599 MainThreadOrGCTaskData<gc::MarkColor> markColor_; 600 601 Vector<JS::GCCellPtr, 0, SystemAllocPolicy> unmarkGrayStack; 602 friend class gc::UnmarkGrayTracer; 603 604 /* Track the state of marking. */ 605 MainThreadOrGCTaskData<MarkingState> state; 606 607 /* Whether we successfully added all edges to the implicit edges table. */ 608 MainThreadOrGCTaskData<bool> haveAllImplicitEdges; 609 610 public: 611 /* 612 * Whether weakmaps can be marked incrementally. 613 * 614 * JSGC_INCREMENTAL_WEAKMAP_ENABLED 615 * pref: javascript.options.mem.incremental_weakmap 616 */ 617 MainThreadOrGCTaskData<bool> incrementalWeakMapMarkingEnabled; 618 619 /* Random number generator state. */ 620 MainThreadOrGCTaskData<mozilla::non_crypto::XorShift128PlusRNG> random; 621 622 #ifdef DEBUG 623 private: 624 /* Assert that start and stop are called with correct ordering. */ 625 MainThreadOrGCTaskData<bool> started; 626 627 /* 628 * Whether to check that atoms traversed are present in atom marking 629 * bitmap. 630 */ 631 MainThreadOrGCTaskData<bool> checkAtomMarking; 632 633 /* 634 * If this is true, all marked objects must belong to a compartment being 635 * GCed. This is used to look for compartment bugs. 636 */ 637 MainThreadOrGCTaskData<bool> strictCompartmentChecking; 638 639 public: 640 /* 641 * The compartment and zone of the object whose trace hook is currently being 642 * called, if any. Used to catch cross-compartment edges traced without use of 643 * TraceCrossCompartmentEdge. 644 */ 645 MainThreadOrGCTaskData<Compartment*> tracingCompartment; 646 MainThreadOrGCTaskData<Zone*> tracingZone; 647 #endif // DEBUG 648 }; 649 650 namespace gc { 651 652 /* 653 * Temporarily change the mark color while this class is on the stack. 654 * 655 * During incremental sweeping this also transitions zones in the 656 * current sweep group into the Mark or MarkGray state as appropriate. 657 */ 658 class MOZ_RAII AutoSetMarkColor { 659 GCMarker& marker_; 660 MarkColor initialColor_; 661 662 public: 663 AutoSetMarkColor(GCMarker& marker, MarkColor newColor) 664 : marker_(marker), initialColor_(marker.markColor()) { 665 marker_.setMarkColor(newColor); 666 } 667 668 AutoSetMarkColor(GCMarker& marker, CellColor newColor) 669 : AutoSetMarkColor(marker, AsMarkColor(newColor)) {} 670 671 ~AutoSetMarkColor() { marker_.setMarkColor(initialColor_); } 672 }; 673 674 } /* namespace gc */ 675 676 } /* namespace js */ 677 678 #endif /* gc_GCMarker_h */