Skip to content

[NativeAOT] Natvis visualization for threadstatic variables. #90473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions src/coreclr/nativeaot/BuildIntegration/NativeAOT.natvis
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,42 @@
</Expand>
</Type>
<Type Name="__ThreadStaticHelper&lt;*&gt;">
<Intrinsic Name="HasModuleStorage" Expression="TypeManagerSlot-&gt;ModuleIndex &lt; tls_CurrentThread.m_numThreadLocalModuleStatics"/>
<Intrinsic Name="GetStorage" Expression="*(__Array&lt;Object&gt;**)tls_CurrentThread.m_pThreadLocalModuleStatics[TypeManagerSlot-&gt;ModuleIndex]"/>
<Intrinsic Name="Get" Expression="*($T1**)(&amp;(*GetStorage()).values)[ClassIndex]"/>
<Intrinsic Name="IsInit" Expression="HasModuleStorage() &amp;&amp; GetStorage() != nullptr &amp;&amp; ClassIndex &lt; GetStorage()-&gt;count &amp;&amp; Get() != nullptr"/>
<Intrinsic Name="GetPerThreadStorage" Expression="(Array*)tls_CurrentThread.m_pThreadLocalStatics"/>
<Intrinsic Name="GetPerModuleStorage" Expression="((Array**)((char*)GetPerThreadStorage() + sizeof(Array)))[TypeManagerSlot-&gt;ModuleIndex]"/>
<Intrinsic Name="GetPerTypeStorage" Expression="((Object**)((char*)GetPerModuleStorage() + sizeof(Array)))[ClassIndex]"/>

<DisplayString Condition="!IsInit()">Null</DisplayString>
<DisplayString Condition="IsInit()">{*Get()}</DisplayString>
<Intrinsic Name="IsInit" Expression="
GetPerThreadStorage() != nullptr &amp;&amp; GetPerModuleStorage()-&gt;m_Length &gt; TypeManagerSlot-&gt;ModuleIndex &amp;&amp;
GetPerModuleStorage() != nullptr &amp;&amp; GetPerModuleStorage()-&gt;m_Length &gt; ClassIndex &amp;&amp;
GetPerTypeStorage() != nullptr "/>

<Intrinsic Name="IsInlined" Expression="ClassIndex &lt; 0"/>

<Intrinsic Name="GetPerThreadStorageInlined" Expression="
root == nullptr ?
nullptr :
root-&gt;m_typeManager == (TypeManager*)TypeManagerSlot-&gt;TypeManager._handleValue ?
root-&gt;m_threadStaticsBase :
nullptr">

<Parameter Name="root" Type="InlinedThreadStaticRoot*"/>
</Intrinsic>

<Intrinsic Name="GetPerTypeStorageInlined" Expression="GetPerThreadStorageInlined(root) ? (Object*)((char*)GetPerThreadStorageInlined(root) - sizeof(char*) - ClassIndex) : nullptr">
<Parameter Name="root" Type="InlinedThreadStaticRoot*"/>
</Intrinsic>

<Intrinsic Name="GetUninlined" Expression="IsInit() ? ($T1*)GetPerTypeStorage() : nullptr"/>
<Intrinsic Name="GetInlined" Expression="($T1*)GetPerTypeStorageInlined(root)">
<Parameter Name="root" Type="InlinedThreadStaticRoot*"/>
</Intrinsic>

<Intrinsic Name="Get" Expression="IsInlined() ? GetInlined(tls_CurrentThread.m_pInlinedThreadLocalStatics) : GetUninlined()"/>

<DisplayString Condition="Get() == nullptr">Null</DisplayString>
<DisplayString Condition="Get() != nullptr">{*Get()}</DisplayString>
<Expand>
<ExpandedItem Condition="IsInit()">Get()</ExpandedItem>
<ExpandedItem Condition="Get() != nullptr">Get()</ExpandedItem>
</Expand>
</Type>
<Type Name="Object" Inheritable="false">
Expand Down
8 changes: 5 additions & 3 deletions src/coreclr/nativeaot/Runtime/thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1281,17 +1281,19 @@ InlinedThreadStaticRoot* Thread::GetInlinedThreadStaticList()
return m_pInlinedThreadLocalStatics;
}

void Thread::RegisterInlinedThreadStaticRoot(InlinedThreadStaticRoot* newRoot)
void Thread::RegisterInlinedThreadStaticRoot(InlinedThreadStaticRoot* newRoot, TypeManager* typeManager)
{
ASSERT(newRoot->m_next == NULL);
newRoot->m_next = m_pInlinedThreadLocalStatics;
m_pInlinedThreadLocalStatics = newRoot;
// we do not need typeManager for runtime needs, but it may be useful for debugging purposes.
newRoot->m_typeManager = typeManager;
}

COOP_PINVOKE_HELPER(void, RhRegisterInlinedThreadStaticRoot, (Object** root))
COOP_PINVOKE_HELPER(void, RhRegisterInlinedThreadStaticRoot, (Object** root, TypeManager* typeManager))
{
Thread* pCurrentThread = ThreadStore::RawGetCurrentThread();
pCurrentThread->RegisterInlinedThreadStaticRoot((InlinedThreadStaticRoot*)root);
pCurrentThread->RegisterInlinedThreadStaticRoot((InlinedThreadStaticRoot*)root, typeManager);
}

// This is function is used to quickly query a value that can uniquely identify a thread
Expand Down
7 changes: 6 additions & 1 deletion src/coreclr/nativeaot/Runtime/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class RuntimeInstance;
class ThreadStore;
class CLREventStatic;
class Thread;
class TypeManager;

#ifdef TARGET_UNIX
#include "UnixContext.h"
Expand Down Expand Up @@ -74,8 +75,12 @@ struct GCFrameRegistration

struct InlinedThreadStaticRoot
{
// The reference to the memory block that stores variables for the current {thread, typeManager} combination
Object* m_threadStaticsBase;
// The next root in the list. All roots in the list belong to the same thread, but to different typeManagers.
InlinedThreadStaticRoot* m_next;
// m_typeManager is used by NativeAOT.natvis when debugging
TypeManager* m_typeManager;
};

struct ThreadBuffer
Expand Down Expand Up @@ -294,7 +299,7 @@ class Thread : private ThreadBuffer
Object** GetThreadStaticStorage();

InlinedThreadStaticRoot* GetInlinedThreadStaticList();
void RegisterInlinedThreadStaticRoot(InlinedThreadStaticRoot* newRoot);
void RegisterInlinedThreadStaticRoot(InlinedThreadStaticRoot* newRoot, TypeManager* typeManager);

NATIVE_CONTEXT* GetInterruptedContext();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal static unsafe object GetInlinedThreadStaticBaseSlow(ref object? threadS
object threadStaticBase = AllocateThreadStaticStorageForType(typeManager, 0);

// register the storage location with the thread for GC reporting.
RuntimeImports.RhRegisterInlinedThreadStaticRoot(ref threadStorage);
RuntimeImports.RhRegisterInlinedThreadStaticRoot(ref threadStorage, typeManager);

// assign the storage block to the storage variable and return
threadStorage = threadStaticBase;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ internal static IntPtr RhGetModuleSection(TypeManagerHandle module, ReadyToRunSe

[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhRegisterInlinedThreadStaticRoot")]
internal static extern void RhRegisterInlinedThreadStaticRoot(ref object? root);
internal static extern void RhRegisterInlinedThreadStaticRoot(ref object? root, TypeManagerHandle module);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhCurrentNativeThreadId")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,22 +203,20 @@ private void CreateNodeCaches()

_threadStatics = new NodeCache<MetadataType, ISymbolDefinitionNode>(CreateThreadStaticsNode);

TypeThreadStaticIndexNode inlinedThreadStatiscIndexNode = null;
if (_inlinedThreadStatics.IsComputed())
{
_inlinedThreadStatiscNode = new ThreadStaticsNode(_inlinedThreadStatics, this);
inlinedThreadStatiscIndexNode = new TypeThreadStaticIndexNode(_inlinedThreadStatiscNode);
}

_typeThreadStaticIndices = new NodeCache<MetadataType, TypeThreadStaticIndexNode>(type =>
{
if (inlinedThreadStatiscIndexNode != null &&
_inlinedThreadStatics.GetOffsets().ContainsKey(type))
if (_inlinedThreadStatics.IsComputed() &&
_inlinedThreadStatics.GetOffsets().ContainsKey(type))
{
return inlinedThreadStatiscIndexNode;
return new TypeThreadStaticIndexNode(type, _inlinedThreadStatiscNode);
}

return new TypeThreadStaticIndexNode(type);
return new TypeThreadStaticIndexNode(type, null);
});

_GCStaticEETypes = new NodeCache<GCPointerMap, GCStaticEETypeNode>((GCPointerMap gcMap) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected override void EmitCode(NodeFactory factory, ref ARM64Emitter encoder,
{
MetadataType target = (MetadataType)Target;
ISortableSymbolNode index = factory.TypeThreadStaticIndex(target);
if (index is TypeThreadStaticIndexNode ti && ti.Type == null)
if (index is TypeThreadStaticIndexNode ti && ti.IsInlined)
{
if (!factory.PreinitializationManager.HasLazyStaticConstructor(target))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected override void EmitCode(NodeFactory factory, ref X64Emitter encoder, bo
{
MetadataType target = (MetadataType)Target;
ISortableSymbolNode index = factory.TypeThreadStaticIndex(target);
if (index is TypeThreadStaticIndexNode ti && ti.Type == null)
if (index is TypeThreadStaticIndexNode ti && ti.IsInlined)
{
if (!factory.PreinitializationManager.HasLazyStaticConstructor(target))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto

if (_type != null)
{

if (factory.PreinitializationManager.HasEagerStaticConstructor(_type))
{
result.Add(new DependencyListEntry(factory.EagerCctorIndirection(_type.GetStaticConstructor()), "Eager .cctor"));
Expand All @@ -90,6 +89,9 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
result.Add(new DependencyListEntry(factory.EagerCctorIndirection(type.GetStaticConstructor()), "Eager .cctor"));
}

// inlined threadstatics do not need the index for execution, but may need it for debug visualization.
result.Add(new DependencyListEntry(factory.TypeThreadStaticIndex(type), "ThreadStatic index for debug visualization"));

ModuleUseBasedDependencyAlgorithm.AddDependenciesDueToModuleUse(ref result, factory, type.Module);
}
}
Expand Down Expand Up @@ -143,5 +145,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer

return comparer.Compare(_type, ((ThreadStaticsNode)other)._type);
}

internal int GetTypeStorageOffset(MetadataType type) => _inlined.GetOffsets()[type];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
objData.RequireInitialPointerAlignment();
objData.AddSymbol(this);

// root
// storage for InlinedThreadStaticRoot instances

// m_threadStaticsBase
objData.EmitZeroPointer();

// m_next
objData.EmitZeroPointer();

// next
// m_typeManager
objData.EmitZeroPointer();

return objData.ToObjectData();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,17 @@ public class TypeThreadStaticIndexNode : DehydratableObjectNode, ISymbolDefiniti
private MetadataType _type;
private ThreadStaticsNode _inlinedThreadStatics;

public TypeThreadStaticIndexNode(MetadataType type)
public TypeThreadStaticIndexNode(MetadataType type, ThreadStaticsNode inlinedThreadStatics)
{
_type = type;
}

public TypeThreadStaticIndexNode(ThreadStaticsNode inlinedThreadStatics)
{
_inlinedThreadStatics = inlinedThreadStatics;
}

public bool IsInlined => _inlinedThreadStatics != null;

public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(_type != null ? nameMangler.NodeMangler.ThreadStaticsIndex(_type) : "_inlinedThreadStaticsIndex");
sb.Append(nameMangler.NodeMangler.ThreadStaticsIndex(_type));
Copy link
Member Author

@VSadov VSadov Aug 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the key change. We used to create just one _inlinedThreadStaticsIndex node for the entire storage block in inlined case and were reusing the node for every type involved.
Now we are back to having individual index nodes for all types that have threadstatics. This allows natvis to know the offsets of each type's storage within the block.

}

public int Offset => 0;
Expand All @@ -46,9 +44,7 @@ protected override ObjectNodeSection GetDehydratedSection(NodeFactory factory)

protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
{
ISymbolDefinitionNode node = _type != null ?
factory.TypeThreadStaticsSymbol(_type) :
_inlinedThreadStatics;
ISymbolDefinitionNode node = _inlinedThreadStatics ?? factory.TypeThreadStaticsSymbol(_type);

return new DependencyList
{
Expand All @@ -66,21 +62,23 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo
int typeTlsIndex = 0;
if (!relocsOnly)
{
if (_type != null)
{
ISymbolDefinitionNode node = factory.TypeThreadStaticsSymbol(_type);
typeTlsIndex = ((ThreadStaticsNode)node).IndexFromBeginningOfArray;
}
else
if (IsInlined)
{
// we use -1 to specify the index of inlined threadstatics,
// which are stored separately from uninlined ones.
typeTlsIndex = -1;
// Inlined threadstatics are stored as a single data block and thus do not need
// an index in the containing storage.
// We use a negative index to indicate that. Any negative value would work.
// For the purpose of natvis we will encode the offset of the type storage within the block.
typeTlsIndex = - (_inlinedThreadStatics.GetTypeStorageOffset(_type) + factory.Target.PointerSize);

// the type of the storage block for inlined threadstatics, if present,
// is serialized as the item #0 among other storage block types.
Debug.Assert(_inlinedThreadStatics.IndexFromBeginningOfArray == 0);
}
else
{
ISymbolDefinitionNode node = factory.TypeThreadStaticsSymbol(_type);
typeTlsIndex = ((ThreadStaticsNode)node).IndexFromBeginningOfArray;
}
}

// needed to construct storage
Expand Down