7

This question is newly relevant in light of the new ref locals and ref return features in the latest versions of C# 7:

With the increased prominence and wider use of managed--or "interior"--pointer variables in C#, occasionally you may need to recover the respective containing Pinnable GC object for such a pointer. For example, if you are passing around a managed pointer to an array element of type T, you might need the array reference T[] itself in order to call (e.g.) Array.Copy(...).

So, from managed code, is there any reasonably legitimate way to recover the containing GC object handle, given either of the following prevalent interior/managed pointer (ref, out, in) uses:

  1. Interior pointer to a (struct or class) field within a GC object instance;
  2. Managed pointer to a (struct or class) element T of Array T[].

Internally in .NET, it appears that the GC uses the following function: /coreclr/master/src/gc/gc.cpp

#ifdef INTERIOR_POINTERS
// will find all heap objects (large and small)
uint8_t* gc_heap::find_object (uint8_t* interior, uint8_t* low)
{
    ....

This code walks through known GC heaps checking for whether the specified interior pointer falls within the range of the known GC object allocations. Obviously, this method is not readily accessible from end-user managed code and, for all I know, may not even be relevant if there's no GC in progress.

I also looked through the new Span<T> and System.Memory libraries, but couldn't find a sequence of manipulations that would recover an (e.g.) array handle if you didn't start by providing it in the first place (whereby the containing handle gets squirreled away in those various structs). In cases where the _pinnable is optional (e.g. Span<T>), the GC handle in the struct is null, so if you don't opt-in from the start, there's no way to get it back.

Summary: Is there any way to recover the containing object handle from a managed pointer?

[edit:] If the managed pointer points to a value-type on the stack, it would be perfectly reasonable for the (putative) handle recovery function to indicate failure by (e.g.) returning 'null'.


Related: How does the C# garbage collector find objects whose only reference is an interior pointer?

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
  • 1
    No, I don't think there's any good or reasonable way to do this. I also don't think it is a reasonable expectation to be doable. You say "in order to call (e.g.) Array.Copy(...)." - I'd say: if that is an expectation, you should be passing a `Span`, which has suitable functionality to mirror `Array.Copy` – Marc Gravell Oct 15 '18 at 22:25
  • @MarcGravell Yes, the **coreclr** code I linked does indeed seem to be implemented as a walk--as opposed to some kind of deterministic O(n) lookup--in order to find the owner. It also looks like the GC itself makes some effort to generally avoid calling `find_object()` if at all possible, falling back to it perhaps only as a last resort. Additional comments by @EricLippert [here](https://stackoverflow.com/q/48287071/#comment83563190_48287071). – Glenn Slayden Oct 15 '18 at 23:42
  • @MarcGravell Also, I entirely agree that `Span` is a most elegant solution to this problem but, as you know, it's a relatively recent innovation for .NET and also seems to one of those features that is most effective when used comprehensively throughout the code base (`async` is another example). Long-term, such features are no-brainers, but in my case I'm working with an extensive base of existing managed code, trying to incrementally introduce some benefit from managed pointers, without extensively revamping hundreds of kLOCs.. – Glenn Slayden Oct 16 '18 at 23:06
  • that's fair enough – Marc Gravell Oct 16 '18 at 23:40

1 Answers1

13

No, recovery of the containing object from an interior pointer is not possible. During GC, interior pointers are translated into corresponding objects thanks to the so-called brick table and plug trees. Given a specified address, the proper brick table entry is calculated and corresponding plug tree is traversed to find the plug within which that address lives. Finally, that plug is scanned object-by-object to find the one that contains the considered address.

The point is those trees are built and available only during GC. So, even if such an "interior pointer recovery" API existed, it would have to wait for the GC and could provide an answer only afterwards (which seems very impractical). Other solutions, like linear memory scanning, would obviously possibly introduce tremendous overhead.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
Konrad Kokosa
  • 16,563
  • 2
  • 36
  • 58
  • 1
    Excellent answer, and congratulations on your new book; I just pre-ordered my copy and can't wait to curl-up with it later this year. – Glenn Slayden Oct 19 '18 at 17:03