Repository navigation
[Foundation] Simplify memory management of NSObjectData for CoreCLR. - #25507
Conversation
On CoreCLR, the tagged memory returned by ObjectiveCMarshal.CreateReferenceTrackingHandle is guaranteed to be stable (same pointer per object) and its lifetime is tied to the GC freeing the instance. This means we can use it directly as our NSObjectData storage, removing the need for: - The TrackedObjectInfo indirection struct (both in C# and native code). - The ConditionalWeakTableused to prevent premature collection of separately-allocated native memory. - The Interlocked.CompareExchange synchronization (since the pointer is always the same). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors CoreCLR toggle-ref support by using the tagged memory returned by ObjectiveCMarshal.CreateReferenceTrackingHandle as the NSObjectData storage directly, removing the previous TrackedObjectInfo indirection and reducing the need for separately allocated native memory on CoreCLR.
Changes:
- Removed the
TrackedObjectInfoindirection and updated CoreCLR bridge code to treat the tagged memory asstruct NSObjectData. - Added
Runtime.GetTaggedMemory (NSObject)and updatedNSObject.GetDatato use tagged memory directly on CoreCLR. - Avoided initializing the
ConditionalWeakTableon CoreCLR (since the separate native allocation path is no longer used there).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/ObjCRuntime/Runtime.CoreCLR.cs | Removes tagged-memory indirection and adds GetTaggedMemory to retrieve tagged memory for CoreCLR. |
| src/Foundation/NSObject2.cs | Switches CoreCLR NSObjectData storage to tagged memory and skips data_table initialization on CoreCLR. |
| runtime/coreclr-bridge.m | Updates native CoreCLR GC callbacks to interpret the tagged pointer as NSObjectData* directly. |
Comments suppressed due to low confidence (1)
src/ObjCRuntime/Runtime.CoreCLR.cs:234
- GetTaggedMemory allocates a reference-tracking GCHandle via ObjectiveCMarshal.CreateReferenceTrackingHandle and then immediately frees it, returning a raw pointer derived from the returned tagged memory span. This adds per-NSObject overhead and also makes correctness depend on a subtle contract: the returned tagged memory pointer must be (a) the same as the tagged memory associated with the long-lived tracking handle created in Runtime.RegisterNSObject, and (b) remain valid even though this temporary handle is freed. To reduce risk and avoid the extra handle churn, consider plumbing the tagged-memory pointer from the existing CreateTrackingGCHandle/Runtime.RegisterNSObject path into NSObject so GetData can use that pointer directly (or otherwise ensure GetData uses the tagged memory from the same tracking handle that native GC callbacks will see).
internal unsafe static void* GetTaggedMemory (NSObject obj)
{
// If https://github.com/dotnet/runtime/issues/128476 is accepted and implemented,
// can just call that new API instead of calling ObjectiveCMarshal.CreateReferenceTrackingHandle and
// freeing the returned handle.
var gchandle = ObjectiveCMarshal.CreateReferenceTrackingHandle (obj, out var info);
// We only care about the tagged memory ('info'), so just free the GCHandle.
// We might want to request an API to just get the tagged memory at some point.
gchandle.Free ();
// The tagged memory pointer is guaranteed to be the same for every call to ObjectiveCMarshal.CreateReferenceTrackingHandle,
// and it will automatically be freed once the 'obj' is freed by the GC (in particular _once the instance is freed_,
// not _once the instance is collectible_, which is a very important distinction for us).
return Unsafe.AsPointer (ref info.GetPinnableReference ());
✅ [PR Build #78a34cf] Build passed (Detect API changes) ✅Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
✅ [PR Build #78a34cf] Build passed (Build packages) ✅Pipeline on Agent |
✅ API diff for current PR / commitNET (empty diffs)✅ API diff vs stableNET (empty diffs)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
✅ [PR Build #78a34cf] Build passed (Build macOS tests) ✅Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
🚀 [CI Build #78a34cf] Test results 🚀Test results✅ All tests passed on VSTS: test results. 🎉 All 183 tests passed 🎉 Tests counts✅ cecil: All 1 tests passed. ( macOS tests✅ Tests on macOS Monterey (12): All 5 tests passed. Html Report (VSDrops) Download Linux Build VerificationPipeline on Agent |
On CoreCLR, the tagged memory returned by ObjectiveCMarshal.CreateReferenceTrackingHandle
is guaranteed to be stable (same pointer per object) and its lifetime is tied to the GC
freeing the instance. This means we can use it directly as our NSObjectData storage,
removing the need for:
collection of separately-allocated native memory.