[NativeAOT] Implements eager finalization of weak references - #75436

Merged
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr
Sep 14, 2022
Merged

[NativeAOT] Implements eager finalization of weak references#75436
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr

Conversation

@VSadov

Copy link
Copy Markdown
Member

Fixes:#75107

  • eager finalization is an important perf and reliability improvement for code that uses a lot of weak references.
  • now that we have eager finalization, simplified the WeakReference<T> and WeakReference implementations a bit.
  • extra method table flags can now be stored in place of ComponentSize, which is rarely used (only arrays and strings need that). A similar pattern as used in CoreClr to increase data density in method tables. The trick allows 16 more bits for type traits, as long as the traits do not apply to arrays.

@VSadov

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov
VSadov requested a review from jkotasSeptember 12, 2022 14:51

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

EagerFinalizer and CriticalFinalizer are not generic type system concepts. They do not need to have a flag here.

We only need to check these when producing EETypes. We can do the check only when building EEType.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is the first time I need to change something in the type system, so Iwas just following existing patterns - like HasFinalizer.
What I hear is that instead of setting this flags we could just check for the same info that we capture in flags, but later.
Do we have a more appropriate example?

We only need to check these when producing EETypes. We can do the check only when building EEType.

Where is that done? At the locations where these flags are currently consumed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should only need to compute these flags here: https://github.com/dotnet/runtime/blob/e3cd737cea578c629a194474fd67d660fc7a9903/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs#L657

Something like:

if (type is MetadataType mdType &&
mdType.Module == context.SystemModule &&
mdType.Name == "WeakReference" pr "WeakReference`1"
mdType.Namespace == "System")
{
flags |= TypeFlags.HasEagerFinalizer;
}

You should not need the one in EETypeBuilderHelpers. Copying from the template type should be enough.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need KeepAlive in a finalizer?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

In theory a finalizer never runs concurrently with itself, thus neither Interlocked.Exchange nor KeepAlive are necessary, since noone could be recycling the handle while we are freeing it.

However, since nongeneric WeakReference is not sealed, it is hard to guarantee anything.

The original code looked like it tried to handle concurrent finalization. It does look like an attempt to handle just a particular kind of misuse, so I kept such assumption.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I guess we can remove both Interlocked.Exchange and KeepAlive. If things are broken, they can be broken in many ways.

I hope deriving from WeakReference or at least overriding the finalizer and doing weird things in it is not a common practice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to match what CoreCLR does (work towards sharing the code eventually).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

CoreCLR has a bunch of code that handles COM and I am not very familiar with requirements. It looks a lot more complicated, perhaps because it is in native code and maybe because of the COM stuff.

Ignoring COM, there is nothing AOT specific here. WeakReferences are just thin facades to GC and handles, which are the same for CoreCLR.
CoreCLR could be trivially switched to use managed implementation (which seems simpler), just would need to handle the COM stuff.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The COM stuff is for http://github.com/microsoft/cswinRT/ support. If once we want to make nativeaot work well for cswinrt, we will need to implement the COM stuff too.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

we will need to implement the COM stuff too

I'd hope there is a way to do that in managed code.
There is some code in NativeAOT under #if ENABLE_WINRT, but maybe it is a different kind of WINRT.

@VSadovVSadovSep 13, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I looked at the native implementation a bit more. The COM is really about detecting RCWs and storing them via a different kind of handle.

The native implementation seems to be adding a lot of complexity just for being native. There is gc protection, for the reference itself and for the referenced object.

There is also a spinlock in the setter. Since the kind of the handle can be changing, the assignment is not a single operation.

There is also a comment saying that the lock synchronizes with finalization done by GC, but GC does not run concurrently with FCalls, (not the blocking mark phases when we know which finalizables are not reachable), so I am not sure the spinlock helps with that. Besides, the getter does not take the spinlock anyways. There is comment saying it is ok, but it is not very convincing. If handle recycling is possible, it would be possible to fetch an object of a wrong type and that is still a GC hole.

I think with the eager finalization the handle recycling is not possible in WeakReference<T>, because eager finalization does not run concurrently with managed code. The same should hold for the nongeneric WeakReference as well, unless it is overriden, but then there are endless opportunities for breakage.
Same should hold for the FCalls as well. I see some GCX_PREEMP(); in the code, but gc.pThis protection should work as KeepAlive.

Maybe it is worth to actually make the native implementation closer to managed, or even switch to managed, if COM stuff can be handled via FCall callouts. Hard to tell without actually trying.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think uintptr_t would be more appropriate type to use for the handle.

Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.h Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should match the tricks that we do for this in MethodTable. It is 0x80000000 so that the JIT can optimize this as sign check. Also, the flags are fetched as dword so that it can use the smaller instructions (on x64).

@VSadovVSadovSep 12, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Like - merge the Flags and FlagsEx into one uint32 and move the HasComponentSize to the sign bit?

or just move the bit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Check the codegen for go_through_object for CoreCLR and make sure that it is as good.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Right, we want mT->HasComponentSize() in the following just be a sign check.
(now it probably loads a short into a 32bit register and does a bit test)

inlinesize_tmy_get_size (Object*ob)
{
MethodTable*mT=header(ob)->GetMethodTable();
return (mT->GetBaseSize() +
(mT->HasComponentSize() ?
((size_t)((CObjectHeader*)ob)->GetNumComponents() *mT->RawGetComponentSize()) : 0));
}

The perf here is typically gated by the cache misses when fetching the mT pointer - we are looking at headers of semi-random objects, and that is often a miss.

It would not hurt if codegen for HasComponentSize is tighter though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is subtle change. Target property is virtual and it call be overridden to do whatever in the inherited type.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I did not even think the Target could be virtual. GetGeneration makes even less sense if Target is overridden.

I guess I will revert to the original implementation, except the KeepAlive(wo);. We do not need to protect the handle once we fetched the obj. The handle can be gone by the time we return to the caller anyways.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The old code also does not do RuntimeImports.RhHandleGet(h) ?? TryGetComTarget(). Another difference.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Oops, sorry. I confused IsAlive and GC.GetGeneration. Both have subtle changes if Target is overridden.
I'll revert both to the old implementation.

Comment threadsrc/coreclr/tools/Common/Internal/Runtime/EETypeBuilderHelpers.cs Outdated
Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/MethodTable.cpp Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @noahfalk This is revving the debugger contract.

@VSadovVSadovSep 14, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

There are more changes when native side is updated, but the most observable part is that a few MT flags have moved around

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// - type arg count for typedefs,
// - type arg count for generic type definitions MethodTables,

Doesn't hurt to spell it out. Generic type definition MethodTables can be weird because they never show up as allocated.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

fixed the comment, on the native side as well.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment on lines 137 to 138

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We made this a single int on the managed side and made emission also happen as a single int. Should we collapse to a single integer here too?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The native changes are coming shortly.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is not a lot, but we can optimize important things like computing object size in GC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you update the large comment at the top of the file? We no longer emit/access this as two ushorts, but as a single int.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Updated

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM otherwise. Thanks!

@VSadov

VSadov commented Sep 14, 2022

Copy link
Copy Markdown
MemberAuthor

With the native changes, we have

=== object size computation:

 s = size (oo);00007FF6202E23FA movrcx,qword ptr [r10]00007FF6202E23FD andrcx,0FFFFFFFFFFFFFFF8h00007FF6202E2401 moveax,dword ptr [rcx]00007FF6202E2403 testeax,eaxHasComponentSize => 00007FF6202E2405 jns SVR::gc_heap::mark_object_simple1+4DAh (07FF6202E282Ah) num components => 00007FF6202E240B movedx,dword ptr [r10+8]comp size => 00007FF6202E240F movzxeax,axarr body size => 00007FF6202E2412 imulrdx,rax00007FF6202E2416 jmp SVR::gc_heap::mark_object_simple1+4DCh (07FF6202E282Ch) 

=== figuring whether an instanse is eagerly finalizable:

bool GCToEEInterface::EagerFinalized(Object* obj){00007FF7C9279C07 movr8,rcx if (!obj->GetGCSafeMethodTable()->HasEagerFinalizer())00007FF7C9279C0A andrax,0FFFFFFFFFFFFFFF8h00007FF7C9279C0E movedx,dword ptr [rax]HasEagerFinalizer => 00007FF7C9279C10 testdl,100007FF7C9279C13 je GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) HasComponentSize => 00007FF7C9279C15 testedx,edx00007FF7C9279C17 js GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) 

@VSadov

Copy link
Copy Markdown
MemberAuthor

Thanks!!

@VSadov
VSadov merged commit ad8debf into dotnet:mainSep 14, 2022
@VSadov
VSadov deleted the wr branch September 14, 2022 08:04
@ghostghost locked as resolved and limited conversation to collaborators Oct 14, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@VSadov@jkotas@MichalStrehovsky
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[NativeAOT] Implements eager finalization of weak references - #75436

Merged
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr
Sep 14, 2022
Merged

[NativeAOT] Implements eager finalization of weak references#75436
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr

Conversation

@VSadov

Copy link
Copy Markdown
Member

Fixes:#75107

  • eager finalization is an important perf and reliability improvement for code that uses a lot of weak references.
  • now that we have eager finalization, simplified the WeakReference<T> and WeakReference implementations a bit.
  • extra method table flags can now be stored in place of ComponentSize, which is rarely used (only arrays and strings need that). A similar pattern as used in CoreClr to increase data density in method tables. The trick allows 16 more bits for type traits, as long as the traits do not apply to arrays.

@VSadov

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov
VSadov requested a review from jkotasSeptember 12, 2022 14:51

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

EagerFinalizer and CriticalFinalizer are not generic type system concepts. They do not need to have a flag here.

We only need to check these when producing EETypes. We can do the check only when building EEType.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is the first time I need to change something in the type system, so Iwas just following existing patterns - like HasFinalizer.
What I hear is that instead of setting this flags we could just check for the same info that we capture in flags, but later.
Do we have a more appropriate example?

We only need to check these when producing EETypes. We can do the check only when building EEType.

Where is that done? At the locations where these flags are currently consumed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should only need to compute these flags here: https://github.com/dotnet/runtime/blob/e3cd737cea578c629a194474fd67d660fc7a9903/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs#L657

Something like:

if (type is MetadataType mdType &&
mdType.Module == context.SystemModule &&
mdType.Name == "WeakReference" pr "WeakReference`1"
mdType.Namespace == "System")
{
flags |= TypeFlags.HasEagerFinalizer;
}

You should not need the one in EETypeBuilderHelpers. Copying from the template type should be enough.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need KeepAlive in a finalizer?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

In theory a finalizer never runs concurrently with itself, thus neither Interlocked.Exchange nor KeepAlive are necessary, since noone could be recycling the handle while we are freeing it.

However, since nongeneric WeakReference is not sealed, it is hard to guarantee anything.

The original code looked like it tried to handle concurrent finalization. It does look like an attempt to handle just a particular kind of misuse, so I kept such assumption.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I guess we can remove both Interlocked.Exchange and KeepAlive. If things are broken, they can be broken in many ways.

I hope deriving from WeakReference or at least overriding the finalizer and doing weird things in it is not a common practice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to match what CoreCLR does (work towards sharing the code eventually).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

CoreCLR has a bunch of code that handles COM and I am not very familiar with requirements. It looks a lot more complicated, perhaps because it is in native code and maybe because of the COM stuff.

Ignoring COM, there is nothing AOT specific here. WeakReferences are just thin facades to GC and handles, which are the same for CoreCLR.
CoreCLR could be trivially switched to use managed implementation (which seems simpler), just would need to handle the COM stuff.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The COM stuff is for http://github.com/microsoft/cswinRT/ support. If once we want to make nativeaot work well for cswinrt, we will need to implement the COM stuff too.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

we will need to implement the COM stuff too

I'd hope there is a way to do that in managed code.
There is some code in NativeAOT under #if ENABLE_WINRT, but maybe it is a different kind of WINRT.

@VSadovVSadovSep 13, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I looked at the native implementation a bit more. The COM is really about detecting RCWs and storing them via a different kind of handle.

The native implementation seems to be adding a lot of complexity just for being native. There is gc protection, for the reference itself and for the referenced object.

There is also a spinlock in the setter. Since the kind of the handle can be changing, the assignment is not a single operation.

There is also a comment saying that the lock synchronizes with finalization done by GC, but GC does not run concurrently with FCalls, (not the blocking mark phases when we know which finalizables are not reachable), so I am not sure the spinlock helps with that. Besides, the getter does not take the spinlock anyways. There is comment saying it is ok, but it is not very convincing. If handle recycling is possible, it would be possible to fetch an object of a wrong type and that is still a GC hole.

I think with the eager finalization the handle recycling is not possible in WeakReference<T>, because eager finalization does not run concurrently with managed code. The same should hold for the nongeneric WeakReference as well, unless it is overriden, but then there are endless opportunities for breakage.
Same should hold for the FCalls as well. I see some GCX_PREEMP(); in the code, but gc.pThis protection should work as KeepAlive.

Maybe it is worth to actually make the native implementation closer to managed, or even switch to managed, if COM stuff can be handled via FCall callouts. Hard to tell without actually trying.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think uintptr_t would be more appropriate type to use for the handle.

Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.h Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should match the tricks that we do for this in MethodTable. It is 0x80000000 so that the JIT can optimize this as sign check. Also, the flags are fetched as dword so that it can use the smaller instructions (on x64).

@VSadovVSadovSep 12, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Like - merge the Flags and FlagsEx into one uint32 and move the HasComponentSize to the sign bit?

or just move the bit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Check the codegen for go_through_object for CoreCLR and make sure that it is as good.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Right, we want mT->HasComponentSize() in the following just be a sign check.
(now it probably loads a short into a 32bit register and does a bit test)

inlinesize_tmy_get_size (Object*ob)
{
MethodTable*mT=header(ob)->GetMethodTable();
return (mT->GetBaseSize() +
(mT->HasComponentSize() ?
((size_t)((CObjectHeader*)ob)->GetNumComponents() *mT->RawGetComponentSize()) : 0));
}

The perf here is typically gated by the cache misses when fetching the mT pointer - we are looking at headers of semi-random objects, and that is often a miss.

It would not hurt if codegen for HasComponentSize is tighter though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is subtle change. Target property is virtual and it call be overridden to do whatever in the inherited type.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I did not even think the Target could be virtual. GetGeneration makes even less sense if Target is overridden.

I guess I will revert to the original implementation, except the KeepAlive(wo);. We do not need to protect the handle once we fetched the obj. The handle can be gone by the time we return to the caller anyways.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The old code also does not do RuntimeImports.RhHandleGet(h) ?? TryGetComTarget(). Another difference.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Oops, sorry. I confused IsAlive and GC.GetGeneration. Both have subtle changes if Target is overridden.
I'll revert both to the old implementation.

Comment threadsrc/coreclr/tools/Common/Internal/Runtime/EETypeBuilderHelpers.cs Outdated
Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/MethodTable.cpp Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @noahfalk This is revving the debugger contract.

@VSadovVSadovSep 14, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

There are more changes when native side is updated, but the most observable part is that a few MT flags have moved around

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// - type arg count for typedefs,
// - type arg count for generic type definitions MethodTables,

Doesn't hurt to spell it out. Generic type definition MethodTables can be weird because they never show up as allocated.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

fixed the comment, on the native side as well.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment on lines 137 to 138

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We made this a single int on the managed side and made emission also happen as a single int. Should we collapse to a single integer here too?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The native changes are coming shortly.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is not a lot, but we can optimize important things like computing object size in GC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you update the large comment at the top of the file? We no longer emit/access this as two ushorts, but as a single int.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Updated

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM otherwise. Thanks!

@VSadov

VSadov commented Sep 14, 2022

Copy link
Copy Markdown
MemberAuthor

With the native changes, we have

=== object size computation:

 s = size (oo);00007FF6202E23FA movrcx,qword ptr [r10]00007FF6202E23FD andrcx,0FFFFFFFFFFFFFFF8h00007FF6202E2401 moveax,dword ptr [rcx]00007FF6202E2403 testeax,eaxHasComponentSize => 00007FF6202E2405 jns SVR::gc_heap::mark_object_simple1+4DAh (07FF6202E282Ah) num components => 00007FF6202E240B movedx,dword ptr [r10+8]comp size => 00007FF6202E240F movzxeax,axarr body size => 00007FF6202E2412 imulrdx,rax00007FF6202E2416 jmp SVR::gc_heap::mark_object_simple1+4DCh (07FF6202E282Ch) 

=== figuring whether an instanse is eagerly finalizable:

bool GCToEEInterface::EagerFinalized(Object* obj){00007FF7C9279C07 movr8,rcx if (!obj->GetGCSafeMethodTable()->HasEagerFinalizer())00007FF7C9279C0A andrax,0FFFFFFFFFFFFFFF8h00007FF7C9279C0E movedx,dword ptr [rax]HasEagerFinalizer => 00007FF7C9279C10 testdl,100007FF7C9279C13 je GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) HasComponentSize => 00007FF7C9279C15 testedx,edx00007FF7C9279C17 js GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) 

@VSadov

Copy link
Copy Markdown
MemberAuthor

Thanks!!

@VSadov
VSadov merged commit ad8debf into dotnet:mainSep 14, 2022
@VSadov
VSadov deleted the wr branch September 14, 2022 08:04
@ghostghost locked as resolved and limited conversation to collaborators Oct 14, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@VSadov@jkotas@MichalStrehovsky
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[NativeAOT] Implements eager finalization of weak references - #75436

Merged
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr
Sep 14, 2022
Merged

[NativeAOT] Implements eager finalization of weak references#75436
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr

Conversation

@VSadov

Copy link
Copy Markdown
Member

Fixes:#75107

  • eager finalization is an important perf and reliability improvement for code that uses a lot of weak references.
  • now that we have eager finalization, simplified the WeakReference<T> and WeakReference implementations a bit.
  • extra method table flags can now be stored in place of ComponentSize, which is rarely used (only arrays and strings need that). A similar pattern as used in CoreClr to increase data density in method tables. The trick allows 16 more bits for type traits, as long as the traits do not apply to arrays.

@VSadov

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov
VSadov requested a review from jkotasSeptember 12, 2022 14:51

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

EagerFinalizer and CriticalFinalizer are not generic type system concepts. They do not need to have a flag here.

We only need to check these when producing EETypes. We can do the check only when building EEType.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is the first time I need to change something in the type system, so Iwas just following existing patterns - like HasFinalizer.
What I hear is that instead of setting this flags we could just check for the same info that we capture in flags, but later.
Do we have a more appropriate example?

We only need to check these when producing EETypes. We can do the check only when building EEType.

Where is that done? At the locations where these flags are currently consumed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should only need to compute these flags here: https://github.com/dotnet/runtime/blob/e3cd737cea578c629a194474fd67d660fc7a9903/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs#L657

Something like:

if (type is MetadataType mdType &&
mdType.Module == context.SystemModule &&
mdType.Name == "WeakReference" pr "WeakReference`1"
mdType.Namespace == "System")
{
flags |= TypeFlags.HasEagerFinalizer;
}

You should not need the one in EETypeBuilderHelpers. Copying from the template type should be enough.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need KeepAlive in a finalizer?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

In theory a finalizer never runs concurrently with itself, thus neither Interlocked.Exchange nor KeepAlive are necessary, since noone could be recycling the handle while we are freeing it.

However, since nongeneric WeakReference is not sealed, it is hard to guarantee anything.

The original code looked like it tried to handle concurrent finalization. It does look like an attempt to handle just a particular kind of misuse, so I kept such assumption.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I guess we can remove both Interlocked.Exchange and KeepAlive. If things are broken, they can be broken in many ways.

I hope deriving from WeakReference or at least overriding the finalizer and doing weird things in it is not a common practice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to match what CoreCLR does (work towards sharing the code eventually).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

CoreCLR has a bunch of code that handles COM and I am not very familiar with requirements. It looks a lot more complicated, perhaps because it is in native code and maybe because of the COM stuff.

Ignoring COM, there is nothing AOT specific here. WeakReferences are just thin facades to GC and handles, which are the same for CoreCLR.
CoreCLR could be trivially switched to use managed implementation (which seems simpler), just would need to handle the COM stuff.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The COM stuff is for http://github.com/microsoft/cswinRT/ support. If once we want to make nativeaot work well for cswinrt, we will need to implement the COM stuff too.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

we will need to implement the COM stuff too

I'd hope there is a way to do that in managed code.
There is some code in NativeAOT under #if ENABLE_WINRT, but maybe it is a different kind of WINRT.

@VSadovVSadovSep 13, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I looked at the native implementation a bit more. The COM is really about detecting RCWs and storing them via a different kind of handle.

The native implementation seems to be adding a lot of complexity just for being native. There is gc protection, for the reference itself and for the referenced object.

There is also a spinlock in the setter. Since the kind of the handle can be changing, the assignment is not a single operation.

There is also a comment saying that the lock synchronizes with finalization done by GC, but GC does not run concurrently with FCalls, (not the blocking mark phases when we know which finalizables are not reachable), so I am not sure the spinlock helps with that. Besides, the getter does not take the spinlock anyways. There is comment saying it is ok, but it is not very convincing. If handle recycling is possible, it would be possible to fetch an object of a wrong type and that is still a GC hole.

I think with the eager finalization the handle recycling is not possible in WeakReference<T>, because eager finalization does not run concurrently with managed code. The same should hold for the nongeneric WeakReference as well, unless it is overriden, but then there are endless opportunities for breakage.
Same should hold for the FCalls as well. I see some GCX_PREEMP(); in the code, but gc.pThis protection should work as KeepAlive.

Maybe it is worth to actually make the native implementation closer to managed, or even switch to managed, if COM stuff can be handled via FCall callouts. Hard to tell without actually trying.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think uintptr_t would be more appropriate type to use for the handle.

Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.h Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should match the tricks that we do for this in MethodTable. It is 0x80000000 so that the JIT can optimize this as sign check. Also, the flags are fetched as dword so that it can use the smaller instructions (on x64).

@VSadovVSadovSep 12, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Like - merge the Flags and FlagsEx into one uint32 and move the HasComponentSize to the sign bit?

or just move the bit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Check the codegen for go_through_object for CoreCLR and make sure that it is as good.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Right, we want mT->HasComponentSize() in the following just be a sign check.
(now it probably loads a short into a 32bit register and does a bit test)

inlinesize_tmy_get_size (Object*ob)
{
MethodTable*mT=header(ob)->GetMethodTable();
return (mT->GetBaseSize() +
(mT->HasComponentSize() ?
((size_t)((CObjectHeader*)ob)->GetNumComponents() *mT->RawGetComponentSize()) : 0));
}

The perf here is typically gated by the cache misses when fetching the mT pointer - we are looking at headers of semi-random objects, and that is often a miss.

It would not hurt if codegen for HasComponentSize is tighter though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is subtle change. Target property is virtual and it call be overridden to do whatever in the inherited type.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I did not even think the Target could be virtual. GetGeneration makes even less sense if Target is overridden.

I guess I will revert to the original implementation, except the KeepAlive(wo);. We do not need to protect the handle once we fetched the obj. The handle can be gone by the time we return to the caller anyways.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The old code also does not do RuntimeImports.RhHandleGet(h) ?? TryGetComTarget(). Another difference.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Oops, sorry. I confused IsAlive and GC.GetGeneration. Both have subtle changes if Target is overridden.
I'll revert both to the old implementation.

Comment threadsrc/coreclr/tools/Common/Internal/Runtime/EETypeBuilderHelpers.cs Outdated
Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/MethodTable.cpp Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @noahfalk This is revving the debugger contract.

@VSadovVSadovSep 14, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

There are more changes when native side is updated, but the most observable part is that a few MT flags have moved around

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// - type arg count for typedefs,
// - type arg count for generic type definitions MethodTables,

Doesn't hurt to spell it out. Generic type definition MethodTables can be weird because they never show up as allocated.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

fixed the comment, on the native side as well.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment on lines 137 to 138

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We made this a single int on the managed side and made emission also happen as a single int. Should we collapse to a single integer here too?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The native changes are coming shortly.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is not a lot, but we can optimize important things like computing object size in GC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you update the large comment at the top of the file? We no longer emit/access this as two ushorts, but as a single int.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Updated

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM otherwise. Thanks!

@VSadov

VSadov commented Sep 14, 2022

Copy link
Copy Markdown
MemberAuthor

With the native changes, we have

=== object size computation:

 s = size (oo);00007FF6202E23FA movrcx,qword ptr [r10]00007FF6202E23FD andrcx,0FFFFFFFFFFFFFFF8h00007FF6202E2401 moveax,dword ptr [rcx]00007FF6202E2403 testeax,eaxHasComponentSize => 00007FF6202E2405 jns SVR::gc_heap::mark_object_simple1+4DAh (07FF6202E282Ah) num components => 00007FF6202E240B movedx,dword ptr [r10+8]comp size => 00007FF6202E240F movzxeax,axarr body size => 00007FF6202E2412 imulrdx,rax00007FF6202E2416 jmp SVR::gc_heap::mark_object_simple1+4DCh (07FF6202E282Ch) 

=== figuring whether an instanse is eagerly finalizable:

bool GCToEEInterface::EagerFinalized(Object* obj){00007FF7C9279C07 movr8,rcx if (!obj->GetGCSafeMethodTable()->HasEagerFinalizer())00007FF7C9279C0A andrax,0FFFFFFFFFFFFFFF8h00007FF7C9279C0E movedx,dword ptr [rax]HasEagerFinalizer => 00007FF7C9279C10 testdl,100007FF7C9279C13 je GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) HasComponentSize => 00007FF7C9279C15 testedx,edx00007FF7C9279C17 js GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) 

@VSadov

Copy link
Copy Markdown
MemberAuthor

Thanks!!

@VSadov
VSadov merged commit ad8debf into dotnet:mainSep 14, 2022
@VSadov
VSadov deleted the wr branch September 14, 2022 08:04
@ghostghost locked as resolved and limited conversation to collaborators Oct 14, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@VSadov@jkotas@MichalStrehovsky
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[NativeAOT] Implements eager finalization of weak references - #75436

Merged
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr
Sep 14, 2022
Merged

[NativeAOT] Implements eager finalization of weak references#75436
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr

Conversation

@VSadov

Copy link
Copy Markdown
Member

Fixes:#75107

  • eager finalization is an important perf and reliability improvement for code that uses a lot of weak references.
  • now that we have eager finalization, simplified the WeakReference<T> and WeakReference implementations a bit.
  • extra method table flags can now be stored in place of ComponentSize, which is rarely used (only arrays and strings need that). A similar pattern as used in CoreClr to increase data density in method tables. The trick allows 16 more bits for type traits, as long as the traits do not apply to arrays.

@VSadov

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov
VSadov requested a review from jkotasSeptember 12, 2022 14:51

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

EagerFinalizer and CriticalFinalizer are not generic type system concepts. They do not need to have a flag here.

We only need to check these when producing EETypes. We can do the check only when building EEType.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is the first time I need to change something in the type system, so Iwas just following existing patterns - like HasFinalizer.
What I hear is that instead of setting this flags we could just check for the same info that we capture in flags, but later.
Do we have a more appropriate example?

We only need to check these when producing EETypes. We can do the check only when building EEType.

Where is that done? At the locations where these flags are currently consumed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should only need to compute these flags here: https://github.com/dotnet/runtime/blob/e3cd737cea578c629a194474fd67d660fc7a9903/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs#L657

Something like:

if (type is MetadataType mdType &&
mdType.Module == context.SystemModule &&
mdType.Name == "WeakReference" pr "WeakReference`1"
mdType.Namespace == "System")
{
flags |= TypeFlags.HasEagerFinalizer;
}

You should not need the one in EETypeBuilderHelpers. Copying from the template type should be enough.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need KeepAlive in a finalizer?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

In theory a finalizer never runs concurrently with itself, thus neither Interlocked.Exchange nor KeepAlive are necessary, since noone could be recycling the handle while we are freeing it.

However, since nongeneric WeakReference is not sealed, it is hard to guarantee anything.

The original code looked like it tried to handle concurrent finalization. It does look like an attempt to handle just a particular kind of misuse, so I kept such assumption.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I guess we can remove both Interlocked.Exchange and KeepAlive. If things are broken, they can be broken in many ways.

I hope deriving from WeakReference or at least overriding the finalizer and doing weird things in it is not a common practice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to match what CoreCLR does (work towards sharing the code eventually).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

CoreCLR has a bunch of code that handles COM and I am not very familiar with requirements. It looks a lot more complicated, perhaps because it is in native code and maybe because of the COM stuff.

Ignoring COM, there is nothing AOT specific here. WeakReferences are just thin facades to GC and handles, which are the same for CoreCLR.
CoreCLR could be trivially switched to use managed implementation (which seems simpler), just would need to handle the COM stuff.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The COM stuff is for http://github.com/microsoft/cswinRT/ support. If once we want to make nativeaot work well for cswinrt, we will need to implement the COM stuff too.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

we will need to implement the COM stuff too

I'd hope there is a way to do that in managed code.
There is some code in NativeAOT under #if ENABLE_WINRT, but maybe it is a different kind of WINRT.

@VSadovVSadovSep 13, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I looked at the native implementation a bit more. The COM is really about detecting RCWs and storing them via a different kind of handle.

The native implementation seems to be adding a lot of complexity just for being native. There is gc protection, for the reference itself and for the referenced object.

There is also a spinlock in the setter. Since the kind of the handle can be changing, the assignment is not a single operation.

There is also a comment saying that the lock synchronizes with finalization done by GC, but GC does not run concurrently with FCalls, (not the blocking mark phases when we know which finalizables are not reachable), so I am not sure the spinlock helps with that. Besides, the getter does not take the spinlock anyways. There is comment saying it is ok, but it is not very convincing. If handle recycling is possible, it would be possible to fetch an object of a wrong type and that is still a GC hole.

I think with the eager finalization the handle recycling is not possible in WeakReference<T>, because eager finalization does not run concurrently with managed code. The same should hold for the nongeneric WeakReference as well, unless it is overriden, but then there are endless opportunities for breakage.
Same should hold for the FCalls as well. I see some GCX_PREEMP(); in the code, but gc.pThis protection should work as KeepAlive.

Maybe it is worth to actually make the native implementation closer to managed, or even switch to managed, if COM stuff can be handled via FCall callouts. Hard to tell without actually trying.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think uintptr_t would be more appropriate type to use for the handle.

Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.h Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should match the tricks that we do for this in MethodTable. It is 0x80000000 so that the JIT can optimize this as sign check. Also, the flags are fetched as dword so that it can use the smaller instructions (on x64).

@VSadovVSadovSep 12, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Like - merge the Flags and FlagsEx into one uint32 and move the HasComponentSize to the sign bit?

or just move the bit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Check the codegen for go_through_object for CoreCLR and make sure that it is as good.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Right, we want mT->HasComponentSize() in the following just be a sign check.
(now it probably loads a short into a 32bit register and does a bit test)

inlinesize_tmy_get_size (Object*ob)
{
MethodTable*mT=header(ob)->GetMethodTable();
return (mT->GetBaseSize() +
(mT->HasComponentSize() ?
((size_t)((CObjectHeader*)ob)->GetNumComponents() *mT->RawGetComponentSize()) : 0));
}

The perf here is typically gated by the cache misses when fetching the mT pointer - we are looking at headers of semi-random objects, and that is often a miss.

It would not hurt if codegen for HasComponentSize is tighter though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is subtle change. Target property is virtual and it call be overridden to do whatever in the inherited type.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I did not even think the Target could be virtual. GetGeneration makes even less sense if Target is overridden.

I guess I will revert to the original implementation, except the KeepAlive(wo);. We do not need to protect the handle once we fetched the obj. The handle can be gone by the time we return to the caller anyways.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The old code also does not do RuntimeImports.RhHandleGet(h) ?? TryGetComTarget(). Another difference.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Oops, sorry. I confused IsAlive and GC.GetGeneration. Both have subtle changes if Target is overridden.
I'll revert both to the old implementation.

Comment threadsrc/coreclr/tools/Common/Internal/Runtime/EETypeBuilderHelpers.cs Outdated
Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/MethodTable.cpp Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @noahfalk This is revving the debugger contract.

@VSadovVSadovSep 14, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

There are more changes when native side is updated, but the most observable part is that a few MT flags have moved around

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// - type arg count for typedefs,
// - type arg count for generic type definitions MethodTables,

Doesn't hurt to spell it out. Generic type definition MethodTables can be weird because they never show up as allocated.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

fixed the comment, on the native side as well.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment on lines 137 to 138

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We made this a single int on the managed side and made emission also happen as a single int. Should we collapse to a single integer here too?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The native changes are coming shortly.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is not a lot, but we can optimize important things like computing object size in GC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you update the large comment at the top of the file? We no longer emit/access this as two ushorts, but as a single int.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Updated

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM otherwise. Thanks!

@VSadov

VSadov commented Sep 14, 2022

Copy link
Copy Markdown
MemberAuthor

With the native changes, we have

=== object size computation:

 s = size (oo);00007FF6202E23FA movrcx,qword ptr [r10]00007FF6202E23FD andrcx,0FFFFFFFFFFFFFFF8h00007FF6202E2401 moveax,dword ptr [rcx]00007FF6202E2403 testeax,eaxHasComponentSize => 00007FF6202E2405 jns SVR::gc_heap::mark_object_simple1+4DAh (07FF6202E282Ah) num components => 00007FF6202E240B movedx,dword ptr [r10+8]comp size => 00007FF6202E240F movzxeax,axarr body size => 00007FF6202E2412 imulrdx,rax00007FF6202E2416 jmp SVR::gc_heap::mark_object_simple1+4DCh (07FF6202E282Ch) 

=== figuring whether an instanse is eagerly finalizable:

bool GCToEEInterface::EagerFinalized(Object* obj){00007FF7C9279C07 movr8,rcx if (!obj->GetGCSafeMethodTable()->HasEagerFinalizer())00007FF7C9279C0A andrax,0FFFFFFFFFFFFFFF8h00007FF7C9279C0E movedx,dword ptr [rax]HasEagerFinalizer => 00007FF7C9279C10 testdl,100007FF7C9279C13 je GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) HasComponentSize => 00007FF7C9279C15 testedx,edx00007FF7C9279C17 js GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) 

@VSadov

Copy link
Copy Markdown
MemberAuthor

Thanks!!

@VSadov
VSadov merged commit ad8debf into dotnet:mainSep 14, 2022
@VSadov
VSadov deleted the wr branch September 14, 2022 08:04
@ghostghost locked as resolved and limited conversation to collaborators Oct 14, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@VSadov@jkotas@MichalStrehovsky
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[NativeAOT] Implements eager finalization of weak references - #75436

Merged
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr
Sep 14, 2022
Merged

[NativeAOT] Implements eager finalization of weak references#75436
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr

Conversation

@VSadov

Copy link
Copy Markdown
Member

Fixes:#75107

  • eager finalization is an important perf and reliability improvement for code that uses a lot of weak references.
  • now that we have eager finalization, simplified the WeakReference<T> and WeakReference implementations a bit.
  • extra method table flags can now be stored in place of ComponentSize, which is rarely used (only arrays and strings need that). A similar pattern as used in CoreClr to increase data density in method tables. The trick allows 16 more bits for type traits, as long as the traits do not apply to arrays.

@VSadov

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov
VSadov requested a review from jkotasSeptember 12, 2022 14:51

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

EagerFinalizer and CriticalFinalizer are not generic type system concepts. They do not need to have a flag here.

We only need to check these when producing EETypes. We can do the check only when building EEType.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is the first time I need to change something in the type system, so Iwas just following existing patterns - like HasFinalizer.
What I hear is that instead of setting this flags we could just check for the same info that we capture in flags, but later.
Do we have a more appropriate example?

We only need to check these when producing EETypes. We can do the check only when building EEType.

Where is that done? At the locations where these flags are currently consumed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should only need to compute these flags here: https://github.com/dotnet/runtime/blob/e3cd737cea578c629a194474fd67d660fc7a9903/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs#L657

Something like:

if (type is MetadataType mdType &&
mdType.Module == context.SystemModule &&
mdType.Name == "WeakReference" pr "WeakReference`1"
mdType.Namespace == "System")
{
flags |= TypeFlags.HasEagerFinalizer;
}

You should not need the one in EETypeBuilderHelpers. Copying from the template type should be enough.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need KeepAlive in a finalizer?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

In theory a finalizer never runs concurrently with itself, thus neither Interlocked.Exchange nor KeepAlive are necessary, since noone could be recycling the handle while we are freeing it.

However, since nongeneric WeakReference is not sealed, it is hard to guarantee anything.

The original code looked like it tried to handle concurrent finalization. It does look like an attempt to handle just a particular kind of misuse, so I kept such assumption.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I guess we can remove both Interlocked.Exchange and KeepAlive. If things are broken, they can be broken in many ways.

I hope deriving from WeakReference or at least overriding the finalizer and doing weird things in it is not a common practice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to match what CoreCLR does (work towards sharing the code eventually).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

CoreCLR has a bunch of code that handles COM and I am not very familiar with requirements. It looks a lot more complicated, perhaps because it is in native code and maybe because of the COM stuff.

Ignoring COM, there is nothing AOT specific here. WeakReferences are just thin facades to GC and handles, which are the same for CoreCLR.
CoreCLR could be trivially switched to use managed implementation (which seems simpler), just would need to handle the COM stuff.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The COM stuff is for http://github.com/microsoft/cswinRT/ support. If once we want to make nativeaot work well for cswinrt, we will need to implement the COM stuff too.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

we will need to implement the COM stuff too

I'd hope there is a way to do that in managed code.
There is some code in NativeAOT under #if ENABLE_WINRT, but maybe it is a different kind of WINRT.

@VSadovVSadovSep 13, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I looked at the native implementation a bit more. The COM is really about detecting RCWs and storing them via a different kind of handle.

The native implementation seems to be adding a lot of complexity just for being native. There is gc protection, for the reference itself and for the referenced object.

There is also a spinlock in the setter. Since the kind of the handle can be changing, the assignment is not a single operation.

There is also a comment saying that the lock synchronizes with finalization done by GC, but GC does not run concurrently with FCalls, (not the blocking mark phases when we know which finalizables are not reachable), so I am not sure the spinlock helps with that. Besides, the getter does not take the spinlock anyways. There is comment saying it is ok, but it is not very convincing. If handle recycling is possible, it would be possible to fetch an object of a wrong type and that is still a GC hole.

I think with the eager finalization the handle recycling is not possible in WeakReference<T>, because eager finalization does not run concurrently with managed code. The same should hold for the nongeneric WeakReference as well, unless it is overriden, but then there are endless opportunities for breakage.
Same should hold for the FCalls as well. I see some GCX_PREEMP(); in the code, but gc.pThis protection should work as KeepAlive.

Maybe it is worth to actually make the native implementation closer to managed, or even switch to managed, if COM stuff can be handled via FCall callouts. Hard to tell without actually trying.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think uintptr_t would be more appropriate type to use for the handle.

Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.h Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should match the tricks that we do for this in MethodTable. It is 0x80000000 so that the JIT can optimize this as sign check. Also, the flags are fetched as dword so that it can use the smaller instructions (on x64).

@VSadovVSadovSep 12, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Like - merge the Flags and FlagsEx into one uint32 and move the HasComponentSize to the sign bit?

or just move the bit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Check the codegen for go_through_object for CoreCLR and make sure that it is as good.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Right, we want mT->HasComponentSize() in the following just be a sign check.
(now it probably loads a short into a 32bit register and does a bit test)

inlinesize_tmy_get_size (Object*ob)
{
MethodTable*mT=header(ob)->GetMethodTable();
return (mT->GetBaseSize() +
(mT->HasComponentSize() ?
((size_t)((CObjectHeader*)ob)->GetNumComponents() *mT->RawGetComponentSize()) : 0));
}

The perf here is typically gated by the cache misses when fetching the mT pointer - we are looking at headers of semi-random objects, and that is often a miss.

It would not hurt if codegen for HasComponentSize is tighter though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is subtle change. Target property is virtual and it call be overridden to do whatever in the inherited type.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I did not even think the Target could be virtual. GetGeneration makes even less sense if Target is overridden.

I guess I will revert to the original implementation, except the KeepAlive(wo);. We do not need to protect the handle once we fetched the obj. The handle can be gone by the time we return to the caller anyways.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The old code also does not do RuntimeImports.RhHandleGet(h) ?? TryGetComTarget(). Another difference.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Oops, sorry. I confused IsAlive and GC.GetGeneration. Both have subtle changes if Target is overridden.
I'll revert both to the old implementation.

Comment threadsrc/coreclr/tools/Common/Internal/Runtime/EETypeBuilderHelpers.cs Outdated
Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/MethodTable.cpp Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @noahfalk This is revving the debugger contract.

@VSadovVSadovSep 14, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

There are more changes when native side is updated, but the most observable part is that a few MT flags have moved around

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// - type arg count for typedefs,
// - type arg count for generic type definitions MethodTables,

Doesn't hurt to spell it out. Generic type definition MethodTables can be weird because they never show up as allocated.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

fixed the comment, on the native side as well.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment on lines 137 to 138

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We made this a single int on the managed side and made emission also happen as a single int. Should we collapse to a single integer here too?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The native changes are coming shortly.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is not a lot, but we can optimize important things like computing object size in GC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you update the large comment at the top of the file? We no longer emit/access this as two ushorts, but as a single int.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Updated

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM otherwise. Thanks!

@VSadov

VSadov commented Sep 14, 2022

Copy link
Copy Markdown
MemberAuthor

With the native changes, we have

=== object size computation:

 s = size (oo);00007FF6202E23FA movrcx,qword ptr [r10]00007FF6202E23FD andrcx,0FFFFFFFFFFFFFFF8h00007FF6202E2401 moveax,dword ptr [rcx]00007FF6202E2403 testeax,eaxHasComponentSize => 00007FF6202E2405 jns SVR::gc_heap::mark_object_simple1+4DAh (07FF6202E282Ah) num components => 00007FF6202E240B movedx,dword ptr [r10+8]comp size => 00007FF6202E240F movzxeax,axarr body size => 00007FF6202E2412 imulrdx,rax00007FF6202E2416 jmp SVR::gc_heap::mark_object_simple1+4DCh (07FF6202E282Ch) 

=== figuring whether an instanse is eagerly finalizable:

bool GCToEEInterface::EagerFinalized(Object* obj){00007FF7C9279C07 movr8,rcx if (!obj->GetGCSafeMethodTable()->HasEagerFinalizer())00007FF7C9279C0A andrax,0FFFFFFFFFFFFFFF8h00007FF7C9279C0E movedx,dword ptr [rax]HasEagerFinalizer => 00007FF7C9279C10 testdl,100007FF7C9279C13 je GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) HasComponentSize => 00007FF7C9279C15 testedx,edx00007FF7C9279C17 js GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) 

@VSadov

Copy link
Copy Markdown
MemberAuthor

Thanks!!

@VSadov
VSadov merged commit ad8debf into dotnet:mainSep 14, 2022
@VSadov
VSadov deleted the wr branch September 14, 2022 08:04
@ghostghost locked as resolved and limited conversation to collaborators Oct 14, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@VSadov@jkotas@MichalStrehovsky
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[NativeAOT] Implements eager finalization of weak references - #75436

Merged
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr
Sep 14, 2022
Merged

[NativeAOT] Implements eager finalization of weak references#75436
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr

Conversation

@VSadov

Copy link
Copy Markdown
Member

Fixes:#75107

  • eager finalization is an important perf and reliability improvement for code that uses a lot of weak references.
  • now that we have eager finalization, simplified the WeakReference<T> and WeakReference implementations a bit.
  • extra method table flags can now be stored in place of ComponentSize, which is rarely used (only arrays and strings need that). A similar pattern as used in CoreClr to increase data density in method tables. The trick allows 16 more bits for type traits, as long as the traits do not apply to arrays.

@VSadov

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov
VSadov requested a review from jkotasSeptember 12, 2022 14:51

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

EagerFinalizer and CriticalFinalizer are not generic type system concepts. They do not need to have a flag here.

We only need to check these when producing EETypes. We can do the check only when building EEType.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is the first time I need to change something in the type system, so Iwas just following existing patterns - like HasFinalizer.
What I hear is that instead of setting this flags we could just check for the same info that we capture in flags, but later.
Do we have a more appropriate example?

We only need to check these when producing EETypes. We can do the check only when building EEType.

Where is that done? At the locations where these flags are currently consumed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should only need to compute these flags here: https://github.com/dotnet/runtime/blob/e3cd737cea578c629a194474fd67d660fc7a9903/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs#L657

Something like:

if (type is MetadataType mdType &&
mdType.Module == context.SystemModule &&
mdType.Name == "WeakReference" pr "WeakReference`1"
mdType.Namespace == "System")
{
flags |= TypeFlags.HasEagerFinalizer;
}

You should not need the one in EETypeBuilderHelpers. Copying from the template type should be enough.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need KeepAlive in a finalizer?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

In theory a finalizer never runs concurrently with itself, thus neither Interlocked.Exchange nor KeepAlive are necessary, since noone could be recycling the handle while we are freeing it.

However, since nongeneric WeakReference is not sealed, it is hard to guarantee anything.

The original code looked like it tried to handle concurrent finalization. It does look like an attempt to handle just a particular kind of misuse, so I kept such assumption.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I guess we can remove both Interlocked.Exchange and KeepAlive. If things are broken, they can be broken in many ways.

I hope deriving from WeakReference or at least overriding the finalizer and doing weird things in it is not a common practice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to match what CoreCLR does (work towards sharing the code eventually).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

CoreCLR has a bunch of code that handles COM and I am not very familiar with requirements. It looks a lot more complicated, perhaps because it is in native code and maybe because of the COM stuff.

Ignoring COM, there is nothing AOT specific here. WeakReferences are just thin facades to GC and handles, which are the same for CoreCLR.
CoreCLR could be trivially switched to use managed implementation (which seems simpler), just would need to handle the COM stuff.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The COM stuff is for http://github.com/microsoft/cswinRT/ support. If once we want to make nativeaot work well for cswinrt, we will need to implement the COM stuff too.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

we will need to implement the COM stuff too

I'd hope there is a way to do that in managed code.
There is some code in NativeAOT under #if ENABLE_WINRT, but maybe it is a different kind of WINRT.

@VSadovVSadovSep 13, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I looked at the native implementation a bit more. The COM is really about detecting RCWs and storing them via a different kind of handle.

The native implementation seems to be adding a lot of complexity just for being native. There is gc protection, for the reference itself and for the referenced object.

There is also a spinlock in the setter. Since the kind of the handle can be changing, the assignment is not a single operation.

There is also a comment saying that the lock synchronizes with finalization done by GC, but GC does not run concurrently with FCalls, (not the blocking mark phases when we know which finalizables are not reachable), so I am not sure the spinlock helps with that. Besides, the getter does not take the spinlock anyways. There is comment saying it is ok, but it is not very convincing. If handle recycling is possible, it would be possible to fetch an object of a wrong type and that is still a GC hole.

I think with the eager finalization the handle recycling is not possible in WeakReference<T>, because eager finalization does not run concurrently with managed code. The same should hold for the nongeneric WeakReference as well, unless it is overriden, but then there are endless opportunities for breakage.
Same should hold for the FCalls as well. I see some GCX_PREEMP(); in the code, but gc.pThis protection should work as KeepAlive.

Maybe it is worth to actually make the native implementation closer to managed, or even switch to managed, if COM stuff can be handled via FCall callouts. Hard to tell without actually trying.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think uintptr_t would be more appropriate type to use for the handle.

Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.h Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should match the tricks that we do for this in MethodTable. It is 0x80000000 so that the JIT can optimize this as sign check. Also, the flags are fetched as dword so that it can use the smaller instructions (on x64).

@VSadovVSadovSep 12, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Like - merge the Flags and FlagsEx into one uint32 and move the HasComponentSize to the sign bit?

or just move the bit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Check the codegen for go_through_object for CoreCLR and make sure that it is as good.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Right, we want mT->HasComponentSize() in the following just be a sign check.
(now it probably loads a short into a 32bit register and does a bit test)

inlinesize_tmy_get_size (Object*ob)
{
MethodTable*mT=header(ob)->GetMethodTable();
return (mT->GetBaseSize() +
(mT->HasComponentSize() ?
((size_t)((CObjectHeader*)ob)->GetNumComponents() *mT->RawGetComponentSize()) : 0));
}

The perf here is typically gated by the cache misses when fetching the mT pointer - we are looking at headers of semi-random objects, and that is often a miss.

It would not hurt if codegen for HasComponentSize is tighter though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is subtle change. Target property is virtual and it call be overridden to do whatever in the inherited type.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I did not even think the Target could be virtual. GetGeneration makes even less sense if Target is overridden.

I guess I will revert to the original implementation, except the KeepAlive(wo);. We do not need to protect the handle once we fetched the obj. The handle can be gone by the time we return to the caller anyways.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The old code also does not do RuntimeImports.RhHandleGet(h) ?? TryGetComTarget(). Another difference.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Oops, sorry. I confused IsAlive and GC.GetGeneration. Both have subtle changes if Target is overridden.
I'll revert both to the old implementation.

Comment threadsrc/coreclr/tools/Common/Internal/Runtime/EETypeBuilderHelpers.cs Outdated
Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/MethodTable.cpp Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @noahfalk This is revving the debugger contract.

@VSadovVSadovSep 14, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

There are more changes when native side is updated, but the most observable part is that a few MT flags have moved around

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// - type arg count for typedefs,
// - type arg count for generic type definitions MethodTables,

Doesn't hurt to spell it out. Generic type definition MethodTables can be weird because they never show up as allocated.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

fixed the comment, on the native side as well.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment on lines 137 to 138

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We made this a single int on the managed side and made emission also happen as a single int. Should we collapse to a single integer here too?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The native changes are coming shortly.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is not a lot, but we can optimize important things like computing object size in GC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you update the large comment at the top of the file? We no longer emit/access this as two ushorts, but as a single int.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Updated

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM otherwise. Thanks!

@VSadov

VSadov commented Sep 14, 2022

Copy link
Copy Markdown
MemberAuthor

With the native changes, we have

=== object size computation:

 s = size (oo);00007FF6202E23FA movrcx,qword ptr [r10]00007FF6202E23FD andrcx,0FFFFFFFFFFFFFFF8h00007FF6202E2401 moveax,dword ptr [rcx]00007FF6202E2403 testeax,eaxHasComponentSize => 00007FF6202E2405 jns SVR::gc_heap::mark_object_simple1+4DAh (07FF6202E282Ah) num components => 00007FF6202E240B movedx,dword ptr [r10+8]comp size => 00007FF6202E240F movzxeax,axarr body size => 00007FF6202E2412 imulrdx,rax00007FF6202E2416 jmp SVR::gc_heap::mark_object_simple1+4DCh (07FF6202E282Ch) 

=== figuring whether an instanse is eagerly finalizable:

bool GCToEEInterface::EagerFinalized(Object* obj){00007FF7C9279C07 movr8,rcx if (!obj->GetGCSafeMethodTable()->HasEagerFinalizer())00007FF7C9279C0A andrax,0FFFFFFFFFFFFFFF8h00007FF7C9279C0E movedx,dword ptr [rax]HasEagerFinalizer => 00007FF7C9279C10 testdl,100007FF7C9279C13 je GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) HasComponentSize => 00007FF7C9279C15 testedx,edx00007FF7C9279C17 js GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) 

@VSadov

Copy link
Copy Markdown
MemberAuthor

Thanks!!

@VSadov
VSadov merged commit ad8debf into dotnet:mainSep 14, 2022
@VSadov
VSadov deleted the wr branch September 14, 2022 08:04
@ghostghost locked as resolved and limited conversation to collaborators Oct 14, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@VSadov@jkotas@MichalStrehovsky
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[NativeAOT] Implements eager finalization of weak references - #75436

Merged
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr
Sep 14, 2022
Merged

[NativeAOT] Implements eager finalization of weak references#75436
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr

Conversation

@VSadov

Copy link
Copy Markdown
Member

Fixes:#75107

  • eager finalization is an important perf and reliability improvement for code that uses a lot of weak references.
  • now that we have eager finalization, simplified the WeakReference<T> and WeakReference implementations a bit.
  • extra method table flags can now be stored in place of ComponentSize, which is rarely used (only arrays and strings need that). A similar pattern as used in CoreClr to increase data density in method tables. The trick allows 16 more bits for type traits, as long as the traits do not apply to arrays.

@VSadov

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov
VSadov requested a review from jkotasSeptember 12, 2022 14:51

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

EagerFinalizer and CriticalFinalizer are not generic type system concepts. They do not need to have a flag here.

We only need to check these when producing EETypes. We can do the check only when building EEType.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is the first time I need to change something in the type system, so Iwas just following existing patterns - like HasFinalizer.
What I hear is that instead of setting this flags we could just check for the same info that we capture in flags, but later.
Do we have a more appropriate example?

We only need to check these when producing EETypes. We can do the check only when building EEType.

Where is that done? At the locations where these flags are currently consumed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should only need to compute these flags here: https://github.com/dotnet/runtime/blob/e3cd737cea578c629a194474fd67d660fc7a9903/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs#L657

Something like:

if (type is MetadataType mdType &&
mdType.Module == context.SystemModule &&
mdType.Name == "WeakReference" pr "WeakReference`1"
mdType.Namespace == "System")
{
flags |= TypeFlags.HasEagerFinalizer;
}

You should not need the one in EETypeBuilderHelpers. Copying from the template type should be enough.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need KeepAlive in a finalizer?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

In theory a finalizer never runs concurrently with itself, thus neither Interlocked.Exchange nor KeepAlive are necessary, since noone could be recycling the handle while we are freeing it.

However, since nongeneric WeakReference is not sealed, it is hard to guarantee anything.

The original code looked like it tried to handle concurrent finalization. It does look like an attempt to handle just a particular kind of misuse, so I kept such assumption.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I guess we can remove both Interlocked.Exchange and KeepAlive. If things are broken, they can be broken in many ways.

I hope deriving from WeakReference or at least overriding the finalizer and doing weird things in it is not a common practice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to match what CoreCLR does (work towards sharing the code eventually).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

CoreCLR has a bunch of code that handles COM and I am not very familiar with requirements. It looks a lot more complicated, perhaps because it is in native code and maybe because of the COM stuff.

Ignoring COM, there is nothing AOT specific here. WeakReferences are just thin facades to GC and handles, which are the same for CoreCLR.
CoreCLR could be trivially switched to use managed implementation (which seems simpler), just would need to handle the COM stuff.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The COM stuff is for http://github.com/microsoft/cswinRT/ support. If once we want to make nativeaot work well for cswinrt, we will need to implement the COM stuff too.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

we will need to implement the COM stuff too

I'd hope there is a way to do that in managed code.
There is some code in NativeAOT under #if ENABLE_WINRT, but maybe it is a different kind of WINRT.

@VSadovVSadovSep 13, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I looked at the native implementation a bit more. The COM is really about detecting RCWs and storing them via a different kind of handle.

The native implementation seems to be adding a lot of complexity just for being native. There is gc protection, for the reference itself and for the referenced object.

There is also a spinlock in the setter. Since the kind of the handle can be changing, the assignment is not a single operation.

There is also a comment saying that the lock synchronizes with finalization done by GC, but GC does not run concurrently with FCalls, (not the blocking mark phases when we know which finalizables are not reachable), so I am not sure the spinlock helps with that. Besides, the getter does not take the spinlock anyways. There is comment saying it is ok, but it is not very convincing. If handle recycling is possible, it would be possible to fetch an object of a wrong type and that is still a GC hole.

I think with the eager finalization the handle recycling is not possible in WeakReference<T>, because eager finalization does not run concurrently with managed code. The same should hold for the nongeneric WeakReference as well, unless it is overriden, but then there are endless opportunities for breakage.
Same should hold for the FCalls as well. I see some GCX_PREEMP(); in the code, but gc.pThis protection should work as KeepAlive.

Maybe it is worth to actually make the native implementation closer to managed, or even switch to managed, if COM stuff can be handled via FCall callouts. Hard to tell without actually trying.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think uintptr_t would be more appropriate type to use for the handle.

Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.h Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should match the tricks that we do for this in MethodTable. It is 0x80000000 so that the JIT can optimize this as sign check. Also, the flags are fetched as dword so that it can use the smaller instructions (on x64).

@VSadovVSadovSep 12, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Like - merge the Flags and FlagsEx into one uint32 and move the HasComponentSize to the sign bit?

or just move the bit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Check the codegen for go_through_object for CoreCLR and make sure that it is as good.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Right, we want mT->HasComponentSize() in the following just be a sign check.
(now it probably loads a short into a 32bit register and does a bit test)

inlinesize_tmy_get_size (Object*ob)
{
MethodTable*mT=header(ob)->GetMethodTable();
return (mT->GetBaseSize() +
(mT->HasComponentSize() ?
((size_t)((CObjectHeader*)ob)->GetNumComponents() *mT->RawGetComponentSize()) : 0));
}

The perf here is typically gated by the cache misses when fetching the mT pointer - we are looking at headers of semi-random objects, and that is often a miss.

It would not hurt if codegen for HasComponentSize is tighter though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is subtle change. Target property is virtual and it call be overridden to do whatever in the inherited type.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I did not even think the Target could be virtual. GetGeneration makes even less sense if Target is overridden.

I guess I will revert to the original implementation, except the KeepAlive(wo);. We do not need to protect the handle once we fetched the obj. The handle can be gone by the time we return to the caller anyways.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The old code also does not do RuntimeImports.RhHandleGet(h) ?? TryGetComTarget(). Another difference.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Oops, sorry. I confused IsAlive and GC.GetGeneration. Both have subtle changes if Target is overridden.
I'll revert both to the old implementation.

Comment threadsrc/coreclr/tools/Common/Internal/Runtime/EETypeBuilderHelpers.cs Outdated
Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/MethodTable.cpp Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @noahfalk This is revving the debugger contract.

@VSadovVSadovSep 14, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

There are more changes when native side is updated, but the most observable part is that a few MT flags have moved around

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// - type arg count for typedefs,
// - type arg count for generic type definitions MethodTables,

Doesn't hurt to spell it out. Generic type definition MethodTables can be weird because they never show up as allocated.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

fixed the comment, on the native side as well.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment on lines 137 to 138

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We made this a single int on the managed side and made emission also happen as a single int. Should we collapse to a single integer here too?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The native changes are coming shortly.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is not a lot, but we can optimize important things like computing object size in GC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you update the large comment at the top of the file? We no longer emit/access this as two ushorts, but as a single int.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Updated

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM otherwise. Thanks!

@VSadov

VSadov commented Sep 14, 2022

Copy link
Copy Markdown
MemberAuthor

With the native changes, we have

=== object size computation:

 s = size (oo);00007FF6202E23FA movrcx,qword ptr [r10]00007FF6202E23FD andrcx,0FFFFFFFFFFFFFFF8h00007FF6202E2401 moveax,dword ptr [rcx]00007FF6202E2403 testeax,eaxHasComponentSize => 00007FF6202E2405 jns SVR::gc_heap::mark_object_simple1+4DAh (07FF6202E282Ah) num components => 00007FF6202E240B movedx,dword ptr [r10+8]comp size => 00007FF6202E240F movzxeax,axarr body size => 00007FF6202E2412 imulrdx,rax00007FF6202E2416 jmp SVR::gc_heap::mark_object_simple1+4DCh (07FF6202E282Ch) 

=== figuring whether an instanse is eagerly finalizable:

bool GCToEEInterface::EagerFinalized(Object* obj){00007FF7C9279C07 movr8,rcx if (!obj->GetGCSafeMethodTable()->HasEagerFinalizer())00007FF7C9279C0A andrax,0FFFFFFFFFFFFFFF8h00007FF7C9279C0E movedx,dword ptr [rax]HasEagerFinalizer => 00007FF7C9279C10 testdl,100007FF7C9279C13 je GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) HasComponentSize => 00007FF7C9279C15 testedx,edx00007FF7C9279C17 js GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) 

@VSadov

Copy link
Copy Markdown
MemberAuthor

Thanks!!

@VSadov
VSadov merged commit ad8debf into dotnet:mainSep 14, 2022
@VSadov
VSadov deleted the wr branch September 14, 2022 08:04
@ghostghost locked as resolved and limited conversation to collaborators Oct 14, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@VSadov@jkotas@MichalStrehovsky
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[NativeAOT] Implements eager finalization of weak references - #75436

Merged
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr
Sep 14, 2022
Merged

[NativeAOT] Implements eager finalization of weak references#75436
VSadov merged 19 commits into
dotnet:mainfrom
VSadov:wr

Conversation

@VSadov

Copy link
Copy Markdown
Member

Fixes:#75107

  • eager finalization is an important perf and reliability improvement for code that uses a lot of weak references.
  • now that we have eager finalization, simplified the WeakReference<T> and WeakReference implementations a bit.
  • extra method table flags can now be stored in place of ComponentSize, which is rarely used (only arrays and strings need that). A similar pattern as used in CoreClr to increase data density in method tables. The trick allows 16 more bits for type traits, as long as the traits do not apply to arrays.

@VSadov

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov
VSadov requested a review from jkotasSeptember 12, 2022 14:51

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

EagerFinalizer and CriticalFinalizer are not generic type system concepts. They do not need to have a flag here.

We only need to check these when producing EETypes. We can do the check only when building EEType.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is the first time I need to change something in the type system, so Iwas just following existing patterns - like HasFinalizer.
What I hear is that instead of setting this flags we could just check for the same info that we capture in flags, but later.
Do we have a more appropriate example?

We only need to check these when producing EETypes. We can do the check only when building EEType.

Where is that done? At the locations where these flags are currently consumed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should only need to compute these flags here: https://github.com/dotnet/runtime/blob/e3cd737cea578c629a194474fd67d660fc7a9903/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs#L657

Something like:

if (type is MetadataType mdType &&
mdType.Module == context.SystemModule &&
mdType.Name == "WeakReference" pr "WeakReference`1"
mdType.Namespace == "System")
{
flags |= TypeFlags.HasEagerFinalizer;
}

You should not need the one in EETypeBuilderHelpers. Copying from the template type should be enough.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need KeepAlive in a finalizer?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

In theory a finalizer never runs concurrently with itself, thus neither Interlocked.Exchange nor KeepAlive are necessary, since noone could be recycling the handle while we are freeing it.

However, since nongeneric WeakReference is not sealed, it is hard to guarantee anything.

The original code looked like it tried to handle concurrent finalization. It does look like an attempt to handle just a particular kind of misuse, so I kept such assumption.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I guess we can remove both Interlocked.Exchange and KeepAlive. If things are broken, they can be broken in many ways.

I hope deriving from WeakReference or at least overriding the finalizer and doing weird things in it is not a common practice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to match what CoreCLR does (work towards sharing the code eventually).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

CoreCLR has a bunch of code that handles COM and I am not very familiar with requirements. It looks a lot more complicated, perhaps because it is in native code and maybe because of the COM stuff.

Ignoring COM, there is nothing AOT specific here. WeakReferences are just thin facades to GC and handles, which are the same for CoreCLR.
CoreCLR could be trivially switched to use managed implementation (which seems simpler), just would need to handle the COM stuff.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The COM stuff is for http://github.com/microsoft/cswinRT/ support. If once we want to make nativeaot work well for cswinrt, we will need to implement the COM stuff too.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

we will need to implement the COM stuff too

I'd hope there is a way to do that in managed code.
There is some code in NativeAOT under #if ENABLE_WINRT, but maybe it is a different kind of WINRT.

@VSadovVSadovSep 13, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I looked at the native implementation a bit more. The COM is really about detecting RCWs and storing them via a different kind of handle.

The native implementation seems to be adding a lot of complexity just for being native. There is gc protection, for the reference itself and for the referenced object.

There is also a spinlock in the setter. Since the kind of the handle can be changing, the assignment is not a single operation.

There is also a comment saying that the lock synchronizes with finalization done by GC, but GC does not run concurrently with FCalls, (not the blocking mark phases when we know which finalizables are not reachable), so I am not sure the spinlock helps with that. Besides, the getter does not take the spinlock anyways. There is comment saying it is ok, but it is not very convincing. If handle recycling is possible, it would be possible to fetch an object of a wrong type and that is still a GC hole.

I think with the eager finalization the handle recycling is not possible in WeakReference<T>, because eager finalization does not run concurrently with managed code. The same should hold for the nongeneric WeakReference as well, unless it is overriden, but then there are endless opportunities for breakage.
Same should hold for the FCalls as well. I see some GCX_PREEMP(); in the code, but gc.pThis protection should work as KeepAlive.

Maybe it is worth to actually make the native implementation closer to managed, or even switch to managed, if COM stuff can be handled via FCall callouts. Hard to tell without actually trying.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think uintptr_t would be more appropriate type to use for the handle.

Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.h Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should match the tricks that we do for this in MethodTable. It is 0x80000000 so that the JIT can optimize this as sign check. Also, the flags are fetched as dword so that it can use the smaller instructions (on x64).

@VSadovVSadovSep 12, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Like - merge the Flags and FlagsEx into one uint32 and move the HasComponentSize to the sign bit?

or just move the bit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Check the codegen for go_through_object for CoreCLR and make sure that it is as good.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Right, we want mT->HasComponentSize() in the following just be a sign check.
(now it probably loads a short into a 32bit register and does a bit test)

inlinesize_tmy_get_size (Object*ob)
{
MethodTable*mT=header(ob)->GetMethodTable();
return (mT->GetBaseSize() +
(mT->HasComponentSize() ?
((size_t)((CObjectHeader*)ob)->GetNumComponents() *mT->RawGetComponentSize()) : 0));
}

The perf here is typically gated by the cache misses when fetching the mT pointer - we are looking at headers of semi-random objects, and that is often a miss.

It would not hurt if codegen for HasComponentSize is tighter though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is subtle change. Target property is virtual and it call be overridden to do whatever in the inherited type.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I did not even think the Target could be virtual. GetGeneration makes even less sense if Target is overridden.

I guess I will revert to the original implementation, except the KeepAlive(wo);. We do not need to protect the handle once we fetched the obj. The handle can be gone by the time we return to the caller anyways.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The old code also does not do RuntimeImports.RhHandleGet(h) ?? TryGetComTarget(). Another difference.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Oops, sorry. I confused IsAlive and GC.GetGeneration. Both have subtle changes if Target is overridden.
I'll revert both to the old implementation.

Comment threadsrc/coreclr/tools/Common/Internal/Runtime/EETypeBuilderHelpers.cs Outdated
Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/MethodTable.cpp Outdated
Comment threadsrc/coreclr/nativeaot/Runtime/ObjectLayout.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @noahfalk This is revving the debugger contract.

@VSadovVSadovSep 14, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

There are more changes when native side is updated, but the most observable part is that a few MT flags have moved around

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// - type arg count for typedefs,
// - type arg count for generic type definitions MethodTables,

Doesn't hurt to spell it out. Generic type definition MethodTables can be weird because they never show up as allocated.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

fixed the comment, on the native side as well.

Comment threadsrc/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs Outdated
Comment on lines 137 to 138

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We made this a single int on the managed side and made emission also happen as a single int. Should we collapse to a single integer here too?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

The native changes are coming shortly.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

It is not a lot, but we can optimize important things like computing object size in GC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you update the large comment at the top of the file? We no longer emit/access this as two ushorts, but as a single int.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Updated

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM otherwise. Thanks!

@VSadov

VSadov commented Sep 14, 2022

Copy link
Copy Markdown
MemberAuthor

With the native changes, we have

=== object size computation:

 s = size (oo);00007FF6202E23FA movrcx,qword ptr [r10]00007FF6202E23FD andrcx,0FFFFFFFFFFFFFFF8h00007FF6202E2401 moveax,dword ptr [rcx]00007FF6202E2403 testeax,eaxHasComponentSize => 00007FF6202E2405 jns SVR::gc_heap::mark_object_simple1+4DAh (07FF6202E282Ah) num components => 00007FF6202E240B movedx,dword ptr [r10+8]comp size => 00007FF6202E240F movzxeax,axarr body size => 00007FF6202E2412 imulrdx,rax00007FF6202E2416 jmp SVR::gc_heap::mark_object_simple1+4DCh (07FF6202E282Ch) 

=== figuring whether an instanse is eagerly finalizable:

bool GCToEEInterface::EagerFinalized(Object* obj){00007FF7C9279C07 movr8,rcx if (!obj->GetGCSafeMethodTable()->HasEagerFinalizer())00007FF7C9279C0A andrax,0FFFFFFFFFFFFFFF8h00007FF7C9279C0E movedx,dword ptr [rax]HasEagerFinalizer => 00007FF7C9279C10 testdl,100007FF7C9279C13 je GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) HasComponentSize => 00007FF7C9279C15 testedx,edx00007FF7C9279C17 js GCToEEInterface::EagerFinalized+42h (07FF7C9279C42h) 

@VSadov

Copy link
Copy Markdown
MemberAuthor

Thanks!!

@VSadov
VSadov merged commit ad8debf into dotnet:mainSep 14, 2022
@VSadov
VSadov deleted the wr branch September 14, 2022 08:04
@ghostghost locked as resolved and limited conversation to collaborators Oct 14, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@VSadov@jkotas@MichalStrehovsky