summaryrefslogtreecommitdiff
path: root/src/gc/softwarewritewatch.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/gc/softwarewritewatch.h')
-rw-r--r--src/gc/softwarewritewatch.h339
1 files changed, 339 insertions, 0 deletions
diff --git a/src/gc/softwarewritewatch.h b/src/gc/softwarewritewatch.h
new file mode 100644
index 0000000000..3c8491cecb
--- /dev/null
+++ b/src/gc/softwarewritewatch.h
@@ -0,0 +1,339 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef __SOFTWARE_WRITE_WATCH_H__
+#define __SOFTWARE_WRITE_WATCH_H__
+
+#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
+#ifndef DACCESS_COMPILE
+
+extern void SwitchToWriteWatchBarrier(bool isRuntimeSuspended);
+extern void SwitchToNonWriteWatchBarrier(bool isRuntimeSuspended);
+
+#define SOFTWARE_WRITE_WATCH_AddressToTableByteIndexShift 0xc
+
+extern "C"
+{
+ // Table containing the dirty state. This table is translated to exclude the lowest address it represents, see
+ // TranslateTableToExcludeHeapStartAddress.
+ extern uint8_t *g_sw_ww_table;
+
+ // Write watch may be disabled when it is not needed (between GCs for instance). This indicates whether it is enabled.
+ extern bool g_sw_ww_enabled_for_gc_heap;
+
+ extern uint8_t *g_lowest_address; // start address of the GC heap
+ extern uint8_t *g_highest_address; // end address of the GC heap
+}
+
+class SoftwareWriteWatch
+{
+private:
+ // The granularity of dirty state in the table is one page. Dirtiness is tracked per byte of the table so that
+ // synchronization is not required when changing the dirty state. Shifting-right an address by the following value yields
+ // the byte index of the address into the write watch table. For instance,
+ // GetTable()[address >> AddressToTableByteIndexShift] is the byte that represents the region of memory for 'address'.
+ static const uint8_t AddressToTableByteIndexShift = SOFTWARE_WRITE_WATCH_AddressToTableByteIndexShift;
+
+private:
+ static void VerifyCreated();
+ static void VerifyMemoryRegion(void *baseAddress, size_t regionByteSize);
+ static void VerifyMemoryRegion(void *baseAddress, size_t regionByteSize, void *heapStartAddress, void *heapEndAddress);
+
+public:
+ static uint8_t *GetTable();
+private:
+ static uint8_t *GetUntranslatedTable();
+ static uint8_t *GetUntranslatedTable(uint8_t *table, void *heapStartAddress);
+ static uint8_t *GetUntranslatedTableEnd();
+ static uint8_t *GetUntranslatedTableEnd(uint8_t *table, void *heapEndAddress);
+public:
+ static void InitializeUntranslatedTable(uint8_t *untranslatedTable, void *heapStartAddress);
+private:
+ static void SetUntranslatedTable(uint8_t *untranslatedTable, void *heapStartAddress);
+public:
+ static void SetResizedUntranslatedTable(uint8_t *untranslatedTable, void *heapStartAddress, void *heapEndAddress);
+ static bool IsEnabledForGCHeap();
+ static void EnableForGCHeap();
+ static void DisableForGCHeap();
+private:
+ static void *GetHeapStartAddress();
+ static void *GetHeapEndAddress();
+
+public:
+ static void StaticClose();
+
+private:
+ static size_t GetTableByteIndex(void *address);
+ static void *GetPageAddress(size_t tableByteIndex);
+public:
+ static size_t GetTableByteSize(void *heapStartAddress, void *heapEndAddress);
+ static size_t GetTableStartByteOffset(size_t byteSizeBeforeTable);
+private:
+ static uint8_t *TranslateTableToExcludeHeapStartAddress(uint8_t *table, void *heapStartAddress);
+ static void TranslateToTableRegion(void *baseAddress, size_t regionByteSize, uint8_t **tableBaseAddressRef, size_t *tableRegionByteSizeRef);
+
+public:
+ static void ClearDirty(void *baseAddress, size_t regionByteSize);
+ static void SetDirty(void *address, size_t writeByteSize);
+ static void SetDirtyRegion(void *baseAddress, size_t regionByteSize);
+private:
+ static bool GetDirtyFromBlock(uint8_t *block, uint8_t *firstPageAddressInBlock, size_t startByteIndex, size_t endByteIndex, void **dirtyPages, size_t *dirtyPageIndexRef, size_t dirtyPageCount, bool clearDirty);
+public:
+ static void GetDirty(void *baseAddress, size_t regionByteSize, void **dirtyPages, size_t *dirtyPageCountRef, bool clearDirty, bool isRuntimeSuspended);
+};
+
+inline void SoftwareWriteWatch::VerifyCreated()
+{
+ assert(GetTable() != nullptr);
+ assert(GetHeapStartAddress() != nullptr);
+ assert(GetHeapEndAddress() != nullptr);
+ assert(GetHeapStartAddress() < GetHeapEndAddress());
+}
+
+inline void SoftwareWriteWatch::VerifyMemoryRegion(void *baseAddress, size_t regionByteSize)
+{
+ VerifyMemoryRegion(baseAddress, regionByteSize, GetHeapStartAddress(), GetHeapEndAddress());
+}
+
+inline void SoftwareWriteWatch::VerifyMemoryRegion(
+ void *baseAddress,
+ size_t regionByteSize,
+ void *heapStartAddress,
+ void *heapEndAddress)
+{
+ VerifyCreated();
+ assert(baseAddress != nullptr);
+ assert(heapStartAddress != nullptr);
+ assert(heapStartAddress >= GetHeapStartAddress());
+ assert(heapEndAddress != nullptr);
+ assert(heapEndAddress <= GetHeapEndAddress());
+ assert(baseAddress >= heapStartAddress);
+ assert(baseAddress < heapEndAddress);
+ assert(regionByteSize != 0);
+ assert(regionByteSize <= reinterpret_cast<size_t>(heapEndAddress) - reinterpret_cast<size_t>(baseAddress));
+}
+
+inline uint8_t *SoftwareWriteWatch::GetTable()
+{
+ return g_sw_ww_table;
+}
+
+inline uint8_t *SoftwareWriteWatch::GetUntranslatedTable()
+{
+ VerifyCreated();
+ return GetUntranslatedTable(GetTable(), GetHeapStartAddress());
+}
+
+inline uint8_t *SoftwareWriteWatch::GetUntranslatedTable(uint8_t *table, void *heapStartAddress)
+{
+ assert(table != nullptr);
+ assert(heapStartAddress != nullptr);
+ assert(heapStartAddress >= GetHeapStartAddress());
+
+ uint8_t *untranslatedTable = table + GetTableByteIndex(heapStartAddress);
+ assert(ALIGN_DOWN(untranslatedTable, sizeof(size_t)) == untranslatedTable);
+ return untranslatedTable;
+}
+
+inline uint8_t *SoftwareWriteWatch::GetUntranslatedTableEnd()
+{
+ VerifyCreated();
+ return GetUntranslatedTableEnd(GetTable(), GetHeapEndAddress());
+}
+
+inline uint8_t *SoftwareWriteWatch::GetUntranslatedTableEnd(uint8_t *table, void *heapEndAddress)
+{
+ assert(table != nullptr);
+ assert(heapEndAddress != nullptr);
+ assert(heapEndAddress <= GetHeapEndAddress());
+
+ return ALIGN_UP(&table[GetTableByteIndex(reinterpret_cast<uint8_t *>(heapEndAddress) - 1) + 1], sizeof(size_t));
+}
+
+inline void SoftwareWriteWatch::InitializeUntranslatedTable(uint8_t *untranslatedTable, void *heapStartAddress)
+{
+ assert(GetTable() == nullptr);
+ SetUntranslatedTable(untranslatedTable, heapStartAddress);
+}
+
+inline void SoftwareWriteWatch::SetUntranslatedTable(uint8_t *untranslatedTable, void *heapStartAddress)
+{
+ assert(untranslatedTable != nullptr);
+ assert(ALIGN_DOWN(untranslatedTable, sizeof(size_t)) == untranslatedTable);
+ assert(heapStartAddress != nullptr);
+
+ g_sw_ww_table = TranslateTableToExcludeHeapStartAddress(untranslatedTable, heapStartAddress);
+}
+
+inline void SoftwareWriteWatch::SetResizedUntranslatedTable(
+ uint8_t *untranslatedTable,
+ void *heapStartAddress,
+ void *heapEndAddress)
+{
+ // The runtime needs to be suspended during this call, and background GC threads need to synchronize calls to ClearDirty()
+ // and GetDirty() such that they are not called concurrently with this function
+
+ VerifyCreated();
+ assert(untranslatedTable != nullptr);
+ assert(ALIGN_DOWN(untranslatedTable, sizeof(size_t)) == untranslatedTable);
+ assert(heapStartAddress != nullptr);
+ assert(heapEndAddress != nullptr);
+ assert(heapStartAddress <= GetHeapStartAddress());
+ assert(heapEndAddress >= GetHeapEndAddress());
+ assert(heapStartAddress < GetHeapStartAddress() || heapEndAddress > GetHeapEndAddress());
+
+ uint8_t *oldUntranslatedTable = GetUntranslatedTable();
+ void *oldTableHeapStartAddress = GetHeapStartAddress();
+ size_t oldTableByteSize = GetTableByteSize(oldTableHeapStartAddress, GetHeapEndAddress());
+ SetUntranslatedTable(untranslatedTable, heapStartAddress);
+
+ uint8_t *tableRegionStart = &GetTable()[GetTableByteIndex(oldTableHeapStartAddress)];
+ memcpy(tableRegionStart, oldUntranslatedTable, oldTableByteSize);
+}
+
+inline bool SoftwareWriteWatch::IsEnabledForGCHeap()
+{
+ return g_sw_ww_enabled_for_gc_heap;
+}
+
+inline void SoftwareWriteWatch::EnableForGCHeap()
+{
+ // The runtime needs to be suspended during this call. This is how it currently guarantees that GC heap writes from other
+ // threads between calls to EnableForGCHeap() and DisableForGCHeap() will be tracked.
+
+ VerifyCreated();
+ assert(!IsEnabledForGCHeap());
+
+ g_sw_ww_enabled_for_gc_heap = true;
+ SwitchToWriteWatchBarrier(true);
+}
+
+inline void SoftwareWriteWatch::DisableForGCHeap()
+{
+ // The runtime needs to be suspended during this call. This is how it currently guarantees that GC heap writes from other
+ // threads between calls to EnableForGCHeap() and DisableForGCHeap() will be tracked.
+
+ VerifyCreated();
+ assert(IsEnabledForGCHeap());
+
+ g_sw_ww_enabled_for_gc_heap = false;
+ SwitchToNonWriteWatchBarrier(true);
+}
+
+inline void *SoftwareWriteWatch::GetHeapStartAddress()
+{
+ return g_lowest_address;
+}
+
+inline void *SoftwareWriteWatch::GetHeapEndAddress()
+{
+ return g_highest_address;
+}
+
+inline size_t SoftwareWriteWatch::GetTableByteIndex(void *address)
+{
+ assert(address != nullptr);
+
+ size_t tableByteIndex = reinterpret_cast<size_t>(address) >> AddressToTableByteIndexShift;
+ assert(tableByteIndex != 0);
+ return tableByteIndex;
+}
+
+inline void *SoftwareWriteWatch::GetPageAddress(size_t tableByteIndex)
+{
+ assert(tableByteIndex != 0);
+
+ void *pageAddress = reinterpret_cast<void *>(tableByteIndex << AddressToTableByteIndexShift);
+ assert(pageAddress >= GetHeapStartAddress());
+ assert(pageAddress < GetHeapEndAddress());
+ assert(ALIGN_DOWN(pageAddress, OS_PAGE_SIZE) == pageAddress);
+ return pageAddress;
+}
+
+inline size_t SoftwareWriteWatch::GetTableByteSize(void *heapStartAddress, void *heapEndAddress)
+{
+ assert(heapStartAddress != nullptr);
+ assert(heapEndAddress != nullptr);
+ assert(heapStartAddress < heapEndAddress);
+
+ size_t tableByteSize =
+ GetTableByteIndex(reinterpret_cast<uint8_t *>(heapEndAddress) - 1) - GetTableByteIndex(heapStartAddress) + 1;
+ tableByteSize = ALIGN_UP(tableByteSize, sizeof(size_t));
+ return tableByteSize;
+}
+
+inline size_t SoftwareWriteWatch::GetTableStartByteOffset(size_t byteSizeBeforeTable)
+{
+ return ALIGN_UP(byteSizeBeforeTable, sizeof(size_t)); // start of the table needs to be aligned to size_t
+}
+
+inline uint8_t *SoftwareWriteWatch::TranslateTableToExcludeHeapStartAddress(uint8_t *table, void *heapStartAddress)
+{
+ assert(table != nullptr);
+ assert(heapStartAddress != nullptr);
+
+ // Exclude the table byte index corresponding to the heap start address from the table pointer, so that each lookup in the
+ // table by address does not have to calculate (address - heapStartAddress)
+ return table - GetTableByteIndex(heapStartAddress);
+}
+
+inline void SoftwareWriteWatch::TranslateToTableRegion(
+ void *baseAddress,
+ size_t regionByteSize,
+ uint8_t **tableBaseAddressRef,
+ size_t *tableRegionByteSizeRef)
+{
+ VerifyCreated();
+ VerifyMemoryRegion(baseAddress, regionByteSize);
+ assert(tableBaseAddressRef != nullptr);
+ assert(tableRegionByteSizeRef != nullptr);
+
+ size_t baseAddressTableByteIndex = GetTableByteIndex(baseAddress);
+ *tableBaseAddressRef = &GetTable()[baseAddressTableByteIndex];
+ *tableRegionByteSizeRef =
+ GetTableByteIndex(reinterpret_cast<uint8_t *>(baseAddress) + (regionByteSize - 1)) - baseAddressTableByteIndex + 1;
+}
+
+inline void SoftwareWriteWatch::ClearDirty(void *baseAddress, size_t regionByteSize)
+{
+ VerifyCreated();
+ VerifyMemoryRegion(baseAddress, regionByteSize);
+
+ uint8_t *tableBaseAddress;
+ size_t tableRegionByteSize;
+ TranslateToTableRegion(baseAddress, regionByteSize, &tableBaseAddress, &tableRegionByteSize);
+ memset(tableBaseAddress, 0, tableRegionByteSize);
+}
+
+inline void SoftwareWriteWatch::SetDirty(void *address, size_t writeByteSize)
+{
+ VerifyCreated();
+ VerifyMemoryRegion(address, writeByteSize);
+ assert(address != nullptr);
+ assert(writeByteSize <= sizeof(void *));
+
+ size_t tableByteIndex = GetTableByteIndex(address);
+ assert(GetTableByteIndex(reinterpret_cast<uint8_t *>(address) + (writeByteSize - 1)) == tableByteIndex);
+
+ uint8_t *tableByteAddress = &GetTable()[tableByteIndex];
+ if (*tableByteAddress == 0)
+ {
+ *tableByteAddress = 0xff;
+ }
+}
+
+inline void SoftwareWriteWatch::SetDirtyRegion(void *baseAddress, size_t regionByteSize)
+{
+ VerifyCreated();
+ VerifyMemoryRegion(baseAddress, regionByteSize);
+
+ uint8_t *tableBaseAddress;
+ size_t tableRegionByteSize;
+ TranslateToTableRegion(baseAddress, regionByteSize, &tableBaseAddress, &tableRegionByteSize);
+ memset(tableBaseAddress, ~0, tableRegionByteSize);
+}
+
+#endif // !DACCESS_COMPILE
+#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
+#endif // !__SOFTWARE_WRITE_WATCH_H__