// // Copyright (c) Microsoft Corporation. All rights reserved. // /*******************************************************************/ /* WARNING */ /* This file should be identical in the Bartok and Singularity */ /* depots. Master copy resides in Bartok Depot. Changes should be */ /* made to Bartok Depot and propagated to Singularity Depot. */ /*******************************************************************/ // #define DEBUG namespace System.GCs { using Microsoft.Bartok.Runtime; using System.Threading; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; /* This class implements the ZeroCountTable for DeferredReferenceCounting * Collector. The ZCT is an array of 10000 (Consider dynamic sizing later) * uint entries. Initially, we link all entries together as a free list * using their array index. When object's RC goes to Zero, we take an * entry from free list, and put the object's address in it. In that * object's refState, the first bit(markFlagMask) is set to indicate * that it is in ZCT. When an object's RC becomes larger than 1, it * is removed from ZCT, its corresponding entry is cleared and put at the * front of the freelist. The freelist is a LIFO one. * * - table[0] is never used, so that 0 can be used for check, instead of * using -1 * If ZCT running out of entries, we trigger GC. */ internal unsafe class ZeroCountTable { // Note: 1. in this class, every method should be ManualRefCounted // 2. all methods/fields are static, no need to allocate in // BootstrapMemory. But the space for ZCT is dynamically // allocated in a special pool // 3. As the pool is marked as NonGC, pointers will not be traced, // and the pool will not be scanned, therefore keep RC untouched // Internal variables private static uint freeHead; private static UIntPtr[] zeroCountTable; private static uint maxEntries; private static ZCTGarbagePicker zctGarbagePicker; private static UIntPtr tableSize; [PreInitRefCounts] public static unsafe void Initialize() { maxEntries = 1 << 16; VTable UIntPtrArrayVtable = ((RuntimeType)typeof(UIntPtr[])).classVtable; tableSize = ObjectLayout.ArraySize(UIntPtrArrayVtable, maxEntries); // Allocate a pool for ZCT BumpAllocator entryPool = new BumpAllocator(PageType.NonGC); UIntPtr memStart = MemoryManager.AllocateMemory(tableSize); entryPool.SetZeroedRange(memStart, tableSize); PageManager.SetStaticDataPages(memStart, tableSize); // Initialize ZCT zeroCountTable = (UIntPtr[]) DeferredReferenceCountingCollector. AllocateArray(ref entryPool, UIntPtrArrayVtable, tableSize); VTable.Assert(zeroCountTable != null, @"zeroCountTable != null"); *(uint*)(Magic.addressOf(zeroCountTable)+PostHeader.Size) = maxEntries; VTable.Assert(zeroCountTable.Length == maxEntries, @"zeroCountTable.Length == maxEntries"); // Build ZCT freeEntries list freeHead = 1; for (uint i = 1; i < maxEntries-1; i++) { zeroCountTable[i]=(UIntPtr)(((i+1) << 2) | 0x01); } zeroCountTable[maxEntries-1] = (UIntPtr)0x01; zctGarbagePicker = (ZCTGarbagePicker)BootstrapMemory. Allocate(typeof(ZCTGarbagePicker)); } [Inline] [ManualRefCounts] [DisableBoundsChecks] private static uint GetFreeEntry() { VTable.Assert(freeHead != 0, @"freeHead != 0"); uint entry = freeHead; freeHead = ((uint)zeroCountTable[entry]) >> 2; return entry; } [Inline] [ManualRefCounts] [DisableBoundsChecks] private static void PutFreeEntry(uint index) { zeroCountTable[index] = (UIntPtr)((freeHead << 2) | 0x01); freeHead = index; } [ManualRefCounts] [DisableBoundsChecks] public static void Add(Object obj) { if (freeHead == 0) { GC.Collect(); // GC may add object into the PLC buffer. } uint refState = obj.REF_STATE; if ((refState & DeferredReferenceCountingCollector.markFlagMask) != 0) { return; // GC may already put this object in ZCT } uint position = GetFreeEntry(); if ((refState & DeferredReferenceCountingCollector. acyclicFlagMask) == 0) { uint index = DeferredReferenceCountingCollector.GetPLCIndex(obj); if (index != 0) { DeferredReferenceCountingCollector. SetPLCIndex(obj, 0); DeferredReferenceCountingCollector. removeFromPLCBuffer(index); } } #if DEBUG else { uint index = DeferredReferenceCountingCollector.GetPLCIndex(obj); VTable.Assert(index == 0, @"index == 0"); } #endif // DEBUG zeroCountTable[position] = Magic.addressOf(obj); obj.REF_STATE = position | DeferredReferenceCountingCollector.markFlagMask | DeferredReferenceCountingCollector.countingONFlagMask | (refState & DeferredReferenceCountingCollector.acyclicFlagMask); } [ManualRefCounts] public static void Remove(Object obj) { uint refState = obj.REF_STATE; uint position = refState & DeferredReferenceCountingCollector.refCountMask; PutFreeEntry(position); obj.REF_STATE = DeferredReferenceCountingCollector.countingONFlagMask | (refState & DeferredReferenceCountingCollector.acyclicFlagMask); } [ManualRefCounts] [DisableBoundsChecks] private static unsafe uint PurgeZCT(NonNullReferenceVisitor entryVisitor) { uint purgedSlots = 0; for (uint i = 1; i < maxEntries; i++) { UIntPtr content = zeroCountTable[i]; if (((uint)content & 0x01) != 0) { continue; // Node in free list, skip } purgedSlots++; entryVisitor.Visit(&content); } return purgedSlots; } [ManualRefCounts] public static bool OutOfEntries() { return freeHead == 0; } [ManualRefCounts] public static bool isInZCT(Object obj) { return (obj.REF_STATE & DeferredReferenceCountingCollector. markFlagMask) != 0; } [ManualRefCounts] public static void ProcessZeroCountTable() { uint purgedSlots; do { purgedSlots = PurgeZCT(zctGarbagePicker); } while (purgedSlots > 0); } public static long Size { get { return (long)tableSize; } } private class ZCTGarbagePicker : NonNullReferenceVisitor { [ManualRefCounts] internal override unsafe void Visit(UIntPtr *loc) { UIntPtr objAddr = *loc; Object obj = Magic.fromAddress(objAddr); // 1. remove from ZCT Remove(obj); // 2. decrement RC on objects retained via multiuseword MultiUseWord muw = MultiUseWord.GetForObject(obj); if (muw.IsMonitorOrInflatedTag()) { MultiUseWord.RefCountGCDeadObjHook(muw); } // 3. add to deallocation list DeferredReferenceCountingCollector.deallocateLazily(obj); } } } }