From 5a67231b2253bdbf2b0497e6ba281b1f84bf0f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 6 Nov 2026 16:00:57 +0900 Subject: [PATCH 1/2] Exclude asynchronous handle scanning from single-threaded builds Guard asynchronous handle-table dispatch and scanner support with FEATURE_MULTITHREADING. Assert synchronous scan requests without rewriting caller inputs, while preserving handle-table layout and multithreaded behavior. This provides a 2,356 byte size saving for the size-optimized WASI NativeAOT Release Hello World, shrinking it from 825,570 to 823,214 bytes. Validated WASI Release/Checked handle-GC probes and Preinitialization, and built Windows x64 Debug workstation/server GC runtimes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9bc3dfee-1040-4648-bdad-86922a749613 --- src/coreclr/gc/handletable.cpp | 12 ++++++++++++ src/coreclr/gc/handletablepriv.h | 4 ++++ src/coreclr/gc/handletablescan.cpp | 10 ++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/coreclr/gc/handletable.cpp b/src/coreclr/gc/handletable.cpp index 7f6cdd4483a3d9..d082ac946d6b63 100644 --- a/src/coreclr/gc/handletable.cpp +++ b/src/coreclr/gc/handletable.cpp @@ -547,8 +547,12 @@ void HndEnumHandles(HHANDLETABLE hTable, const uint32_t *puType, uint32_t uTypeC // choose a scanning method based on the async flag TABLESCANPROC pfnScanTable = TableScanHandles; +#ifdef FEATURE_MULTITHREADING if (fAsync) pfnScanTable = xxxTableScanHandlesAsync; +#else // FEATURE_MULTITHREADING + _ASSERTE(!fAsync); +#endif // FEATURE_MULTITHREADING { // acquire the handle manager lock @@ -663,14 +667,22 @@ void HndScanHandlesForGC(HHANDLETABLE hTable, HANDLESCANPROC scanProc, uintptr_t // choose a scanning method based on the async flag TABLESCANPROC pfnScanTable = TableScanHandles; +#ifdef FEATURE_MULTITHREADING if (flags & HNDGCF_ASYNC) { pfnScanTable = xxxTableScanHandlesAsync; } +#else // FEATURE_MULTITHREADING + _ASSERTE((flags & HNDGCF_ASYNC) == 0); +#endif // FEATURE_MULTITHREADING { // lock the table down for concurrent GC only +#ifdef FEATURE_MULTITHREADING CrstHolderWithState ch(&pTable->Lock, (flags & HNDGCF_ASYNC) != 0); +#else // FEATURE_MULTITHREADING + CrstHolderWithState ch(&pTable->Lock, false); +#endif // FEATURE_MULTITHREADING // perform the scan pfnScanTable(pTable, types, typeCount, pfnSegment, pfnBlock, &info, &ch); diff --git a/src/coreclr/gc/handletablepriv.h b/src/coreclr/gc/handletablepriv.h index 82b39fe4bf1322..3917d2c606e177 100644 --- a/src/coreclr/gc/handletablepriv.h +++ b/src/coreclr/gc/handletablepriv.h @@ -889,6 +889,7 @@ void CALLBACK TableScanHandles(PTR_HandleTable pTable, CrstHolderWithState *pCrstHolder); +#ifdef FEATURE_MULTITHREADING /* * xxxTableScanHandlesAsync * @@ -902,6 +903,7 @@ void CALLBACK xxxTableScanHandlesAsync(PTR_HandleTable pTable, BLOCKSCANPROC pfnBlockHandler, ScanCallbackInfo *pInfo, CrstHolderWithState *pCrstHolder); +#endif // FEATURE_MULTITHREADING /* @@ -1027,6 +1029,7 @@ void CALLBACK BlockResetAgeMapForBlocks(PTR_TableSegment pSegment, uint32_t uBlo void CALLBACK BlockVerifyAgeMapForBlocks(PTR_TableSegment pSegment, uint32_t uBlock, uint32_t uCount, ScanCallbackInfo *pInfo); +#ifdef FEATURE_MULTITHREADING /* * xxxAsyncSegmentIterator * @@ -1034,5 +1037,6 @@ void CALLBACK BlockVerifyAgeMapForBlocks(PTR_TableSegment pSegment, uint32_t uBl * */ PTR_TableSegment CALLBACK xxxAsyncSegmentIterator(PTR_HandleTable pTable, TableSegment *pPrevSegment, CrstHolderWithState *pCrstHolder); +#endif // FEATURE_MULTITHREADING /*--------------------------------------------------------------------------*/ diff --git a/src/coreclr/gc/handletablescan.cpp b/src/coreclr/gc/handletablescan.cpp index 26574c9533a478..324c504072afd3 100644 --- a/src/coreclr/gc/handletablescan.cpp +++ b/src/coreclr/gc/handletablescan.cpp @@ -193,6 +193,7 @@ If you change any of those algorithm, please verify it by this program: +#ifdef FEATURE_MULTITHREADING /**************************************************************************** * * SUPPORT STRUCTURES FOR ASYNCHRONOUS SCANNING @@ -252,6 +253,7 @@ struct ScanQNode */ ScanRange rgRange[HANDLE_BLOCKS_PER_SEGMENT / 4]; }; +#endif // FEATURE_MULTITHREADING /*--------------------------------------------------------------------------*/ @@ -1076,6 +1078,7 @@ void CALLBACK BlockVerifyAgeMapForBlocks(TableSegment *pSegment, uint32_t uBlock } } +#ifdef FEATURE_MULTITHREADING /* * BlockLockBlocks * @@ -1106,8 +1109,10 @@ void CALLBACK BlockUnlockBlocks(TableSegment *pSegment, uint32_t uBlock, uint32_ for (uCount += uBlock; uBlock < uCount; uBlock++) BlockUnlock(pSegment, uBlock); } +#endif // FEATURE_MULTITHREADING #endif // !DACCESS_COMPILE +#ifdef FEATURE_MULTITHREADING /* * BlockQueueBlocksForAsyncScan * @@ -1382,6 +1387,7 @@ void xxxTableScanQueuedBlocksAsync(PTR_HandleTable pTable, PTR_TableSegment pSeg // reset the "queue tail" pointer to indicate an empty queue pAsyncInfo->pQueueTail = NULL; } +#endif // FEATURE_MULTITHREADING /*--------------------------------------------------------------------------*/ @@ -1567,6 +1573,7 @@ PTR_TableSegment CALLBACK FullSegmentIterator(PTR_HandleTable pTable, PTR_TableS return pNextSegment; } +#ifdef FEATURE_MULTITHREADING /* * xxxAsyncSegmentIterator * @@ -1599,6 +1606,7 @@ PTR_TableSegment CALLBACK xxxAsyncSegmentIterator(PTR_HandleTable pTable, PTR_Ta // call the underlying iterator to get the next segment return pfnCoreIterator(pTable, pPrevSegment, pCrstHolder); } +#endif // FEATURE_MULTITHREADING /*--------------------------------------------------------------------------*/ @@ -1781,6 +1789,7 @@ void CALLBACK TableScanHandles(PTR_HandleTable pTable, } +#ifdef FEATURE_MULTITHREADING /* * xxxTableScanHandlesAsync * @@ -1860,6 +1869,7 @@ void CALLBACK xxxTableScanHandlesAsync(PTR_HandleTable pTable, // unlink our async scanning info from the table pTable->pAsyncScanInfo = NULL; } +#endif // FEATURE_MULTITHREADING #ifdef DACCESS_COMPILE // TableSegment is variable size, where the data up to "rgValue" is static, From 88c4fd7849ab3bec4af1c185c1eae240f43549e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Sat, 7 Nov 2026 14:15:07 +0900 Subject: [PATCH 2/2] Let the linker discard unreachable asynchronous handle scanners Keep FEATURE_MULTITHREADING guards only at asynchronous dispatch sites and remove fine-grained scanner and lock-construction guards, addressing review feedback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b9c54b14-a38c-42b9-89c4-ffacfcc2fe80 --- src/coreclr/gc/handletable.cpp | 4 ---- src/coreclr/gc/handletablepriv.h | 4 ---- src/coreclr/gc/handletablescan.cpp | 10 ---------- 3 files changed, 18 deletions(-) diff --git a/src/coreclr/gc/handletable.cpp b/src/coreclr/gc/handletable.cpp index d082ac946d6b63..9216ec34f3d7c3 100644 --- a/src/coreclr/gc/handletable.cpp +++ b/src/coreclr/gc/handletable.cpp @@ -678,11 +678,7 @@ void HndScanHandlesForGC(HHANDLETABLE hTable, HANDLESCANPROC scanProc, uintptr_t { // lock the table down for concurrent GC only -#ifdef FEATURE_MULTITHREADING CrstHolderWithState ch(&pTable->Lock, (flags & HNDGCF_ASYNC) != 0); -#else // FEATURE_MULTITHREADING - CrstHolderWithState ch(&pTable->Lock, false); -#endif // FEATURE_MULTITHREADING // perform the scan pfnScanTable(pTable, types, typeCount, pfnSegment, pfnBlock, &info, &ch); diff --git a/src/coreclr/gc/handletablepriv.h b/src/coreclr/gc/handletablepriv.h index 3917d2c606e177..82b39fe4bf1322 100644 --- a/src/coreclr/gc/handletablepriv.h +++ b/src/coreclr/gc/handletablepriv.h @@ -889,7 +889,6 @@ void CALLBACK TableScanHandles(PTR_HandleTable pTable, CrstHolderWithState *pCrstHolder); -#ifdef FEATURE_MULTITHREADING /* * xxxTableScanHandlesAsync * @@ -903,7 +902,6 @@ void CALLBACK xxxTableScanHandlesAsync(PTR_HandleTable pTable, BLOCKSCANPROC pfnBlockHandler, ScanCallbackInfo *pInfo, CrstHolderWithState *pCrstHolder); -#endif // FEATURE_MULTITHREADING /* @@ -1029,7 +1027,6 @@ void CALLBACK BlockResetAgeMapForBlocks(PTR_TableSegment pSegment, uint32_t uBlo void CALLBACK BlockVerifyAgeMapForBlocks(PTR_TableSegment pSegment, uint32_t uBlock, uint32_t uCount, ScanCallbackInfo *pInfo); -#ifdef FEATURE_MULTITHREADING /* * xxxAsyncSegmentIterator * @@ -1037,6 +1034,5 @@ void CALLBACK BlockVerifyAgeMapForBlocks(PTR_TableSegment pSegment, uint32_t uBl * */ PTR_TableSegment CALLBACK xxxAsyncSegmentIterator(PTR_HandleTable pTable, TableSegment *pPrevSegment, CrstHolderWithState *pCrstHolder); -#endif // FEATURE_MULTITHREADING /*--------------------------------------------------------------------------*/ diff --git a/src/coreclr/gc/handletablescan.cpp b/src/coreclr/gc/handletablescan.cpp index 324c504072afd3..26574c9533a478 100644 --- a/src/coreclr/gc/handletablescan.cpp +++ b/src/coreclr/gc/handletablescan.cpp @@ -193,7 +193,6 @@ If you change any of those algorithm, please verify it by this program: -#ifdef FEATURE_MULTITHREADING /**************************************************************************** * * SUPPORT STRUCTURES FOR ASYNCHRONOUS SCANNING @@ -253,7 +252,6 @@ struct ScanQNode */ ScanRange rgRange[HANDLE_BLOCKS_PER_SEGMENT / 4]; }; -#endif // FEATURE_MULTITHREADING /*--------------------------------------------------------------------------*/ @@ -1078,7 +1076,6 @@ void CALLBACK BlockVerifyAgeMapForBlocks(TableSegment *pSegment, uint32_t uBlock } } -#ifdef FEATURE_MULTITHREADING /* * BlockLockBlocks * @@ -1109,10 +1106,8 @@ void CALLBACK BlockUnlockBlocks(TableSegment *pSegment, uint32_t uBlock, uint32_ for (uCount += uBlock; uBlock < uCount; uBlock++) BlockUnlock(pSegment, uBlock); } -#endif // FEATURE_MULTITHREADING #endif // !DACCESS_COMPILE -#ifdef FEATURE_MULTITHREADING /* * BlockQueueBlocksForAsyncScan * @@ -1387,7 +1382,6 @@ void xxxTableScanQueuedBlocksAsync(PTR_HandleTable pTable, PTR_TableSegment pSeg // reset the "queue tail" pointer to indicate an empty queue pAsyncInfo->pQueueTail = NULL; } -#endif // FEATURE_MULTITHREADING /*--------------------------------------------------------------------------*/ @@ -1573,7 +1567,6 @@ PTR_TableSegment CALLBACK FullSegmentIterator(PTR_HandleTable pTable, PTR_TableS return pNextSegment; } -#ifdef FEATURE_MULTITHREADING /* * xxxAsyncSegmentIterator * @@ -1606,7 +1599,6 @@ PTR_TableSegment CALLBACK xxxAsyncSegmentIterator(PTR_HandleTable pTable, PTR_Ta // call the underlying iterator to get the next segment return pfnCoreIterator(pTable, pPrevSegment, pCrstHolder); } -#endif // FEATURE_MULTITHREADING /*--------------------------------------------------------------------------*/ @@ -1789,7 +1781,6 @@ void CALLBACK TableScanHandles(PTR_HandleTable pTable, } -#ifdef FEATURE_MULTITHREADING /* * xxxTableScanHandlesAsync * @@ -1869,7 +1860,6 @@ void CALLBACK xxxTableScanHandlesAsync(PTR_HandleTable pTable, // unlink our async scanning info from the table pTable->pAsyncScanInfo = NULL; } -#endif // FEATURE_MULTITHREADING #ifdef DACCESS_COMPILE // TableSegment is variable size, where the data up to "rgValue" is static,