From 6498f9d2b6c20f4d1df0bdeea0fcfe20f76f98c9 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 2 Oct 2026 08:24:39 -0700 Subject: [PATCH] Fix direct thread-static alignment fallback (#134982) ## Summary Small non-collectible thread-static blocks can be allocated directly inside `ThreadLocalData`. The allocator works from the end of the direct-TLS region and aligns each allocation according to its size. The allocator previously marked this path successful even when the unaligned allocation fit but the required alignment padding did not. In that boundary case, the code retained an invalid direct-TLS result instead of falling back to the dynamically allocated non-collectible thread-static array. This change marks direct-TLS allocation successful only after the aligned allocation is confirmed to fit. Rejected allocations now use the existing non-collectible array path. ## Testing - Windows x86 Checked CoreCLR build. - Added and ran `threadstatic09`, which starts fresh child processes with different amounts of direct-TLS padding and verifies an eight-byte `[ThreadStatic] long` across the allocation boundary. - Verified during development that the regression fails against the unfixed runtime and passes with this change. This is a follow-up to dotnet/runtime#129733 and dotnet/runtime#129749. The original issue is already closed by the earlier PR. > [!NOTE] > This pull request description was generated with GitHub Copilot. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/threadstatics.cpp | 2 +- .../threading/threadstatic/threadstatic09.cs | 72 +++++++++++++++++++ .../threadstatic/threadstatic09.csproj | 13 ++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/tests/baseservices/threading/threadstatic/threadstatic09.cs create mode 100644 src/tests/baseservices/threading/threadstatic/threadstatic09.csproj diff --git a/src/coreclr/vm/threadstatics.cpp b/src/coreclr/vm/threadstatics.cpp index ab7a585215da6e..b5032edffd17c0 100644 --- a/src/coreclr/vm/threadstatics.cpp +++ b/src/coreclr/vm/threadstatics.cpp @@ -750,8 +750,8 @@ void GetTLSIndexForThreadStatic(MethodTable* pMT, bool gcStatic, TLSIndex* pInde { g_directThreadLocalTLSBytesAvailable = newBytesAvailable - alignmentAdjust; newTLSIndex = TLSIndex(TLSIndexType::DirectOnThreadLocalData, actualIndexOffset); + usedDirectOnThreadLocalDataPath = true; } - usedDirectOnThreadLocalDataPath = true; } if (usedDirectOnThreadLocalDataPath) VolatileStore(&g_pMethodTablesForDirectThreadLocalData[IndexOffsetToDirectThreadLocalIndex(newTLSIndex.GetIndexOffset())], pMT); diff --git a/src/tests/baseservices/threading/threadstatic/threadstatic09.cs b/src/tests/baseservices/threading/threadstatic/threadstatic09.cs new file mode 100644 index 00000000000000..fb6084e81f48cf --- /dev/null +++ b/src/tests/baseservices/threading/threadstatic/threadstatic09.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +public static class ThreadStaticAlignmentFallback +{ + private const int Pass = 100; + private const int Fail = -1; + private const int PaddingCount = 12; + + public static int Main() + { + int paddingCount = new Random().Next(PaddingCount); + Console.WriteLine($"Using {paddingCount} padding fields."); + return InitializePadding(paddingCount) && LongStorage.Check() ? Pass : Fail; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool InitializePadding(int count) + { + if (count == 0) + { + return true; + } + + if (!Padding.Check()) + { + return false; + } + + return InitializePadding>(count - 1); + } + + private sealed class Padding + { + [ThreadStatic] + private static int s_value; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Check() + { + if (s_value != 0) + { + return false; + } + + s_value = 42; + return s_value == 42; + } + } + + private static class LongStorage + { + [ThreadStatic] + private static long s_value; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Check() + { + if (Interlocked.Increment(ref s_value) != 1) + { + return false; + } + + s_value = 0x1122334455667788; + return s_value == 0x1122334455667788; + } + } +} diff --git a/src/tests/baseservices/threading/threadstatic/threadstatic09.csproj b/src/tests/baseservices/threading/threadstatic/threadstatic09.csproj new file mode 100644 index 00000000000000..f21acafb595d74 --- /dev/null +++ b/src/tests/baseservices/threading/threadstatic/threadstatic09.csproj @@ -0,0 +1,13 @@ + + + Exe + 1 + true + false + true + true + + + + +