-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Understanding codegen behavior around ref and struct locals #69321
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
The example is equivalent to this: object objRef = { ... };
ref object objRefRef = ref objRef;
Console.WriteLine(objRefRef); Both cases either result in So, yes, it is safe. Practically, the Jit is super pessimistic about |
Tagging subscribers to this area: @JulieLeeMSFT Issue DetailsConsider the following code: struct S {
int Age;
public string Field;
static void Method() {
S local = new S { Field = "lets make this interesting" };
TypedReference tr = __makeref(ref local);
// do some other stuff
S copy = __refvalue(tr, S);
Console.WriteLine(copy.Field);
}
} Is the access of The variable ref struct TypedReference {
ref byte _data;
IntPtr _type;
} So in this case Wanted to get a bit of clarity on this as it's come up a few times in customer chats and is related to the upcoming fast reflection work.
|
Based on conversations with others I worry this is not the case. The case you mentioned uses a reference type which means the value is on the heap, there is an object header and the GC can get full type information. In the case of a Effectively other members of the dotnet/runtime team have claimed this is unsafe. There are responses in this thread that it is safe. Really need clarity on which is the case here. It would also be beneficial to understand why it is safe to help reason about other cases. Is the guarantee that a |
@SingleAccretion I think the question is not what the JIT does today but what the runtime/GC require. That is, would it be considered invalid for a sufficiently advanced compiler (say Mono LLVM or the Unity Burst compiler) to do the requisite analysis and determine that a What about for cases such as The question is basically, does the runtime require that a |
@jaredpar I agree with @SingleAccretion, this appears to be the same logically. I am referencing section III.4.19 for
The key phrase to me is "This instruction enables the passing of dynamically typed references as arguments", which means if that is its purpose it should be enough to extend the lifetime of the entire instance.
This seems to be a different question though. The original statement was top down - does keeping a local extend all fields and the answer is "yes". The above seems to be the inverse, does referencing a field extend the entire enclosing structure, which I believe is "no". |
Which then leads to, a Does a runtime then need to special-case That is, a user could not define their own struct (as below) and expect the same guarantees public ref struct MyTypedReference
{
private readonly ref byte _value;
private readonly IntPtr _type;
} |
@jaredpar do you want to change the title to say "Understanding codegen behavior around ref and struct locals"? 😀 |
@Maoni0 sure. Not quite sure where the boundary is between codegen and GC here. If it's codegen then i'll update the title |
Yes. My answer (modulo the part about To better understand this: let's start with a very simple liveness model: everything is live everywhere. It is always correct. Any compiler can then try and "tighten" the liveness boundaries if it can determine the full set of defs and uses that occur, as long as the functional result of the program is the same, and the source IL is correct (it is in the example above). It is inconsequential whether the There are always exceptions to the rules...But they lay in the realm of |
Whether its collected "too early" is entirely dependent on what the runtime and GC require around liveness tracking. As far as I can tell, there is nothing that requires a regular Otherwise, the runtime is effectively prohibited from doing any kind of promotion, enregistration, or other similar optimizations when a byref exists to any field of a local, even if it was otherwise able to statically prove that the byref was only ever to a single field. And so its a question of, is this actually prohibited by the runtime and a requirement for any conforming implementation. Or, is it simply a byproduct of the current runtime implementation being "not sufficiently advanced". |
-- Noting I'm also perfectly fine being wrong here. I just want to ensure the right question is answered. For the language, it technically matters outside the scope of just our own runtime ( |
This is my primary goal as well. I want to understand what our rules are here, particularly where they intersect C# features. I do feel the original example I posed around |
Let me try and share my understanding on this front. ref struct S
{
public string a;
public string b;
}
S s = default;
var tr = MakeTR(ref s)
// All of 's' is valid. An address of the local was taken.
S s = default;
var tr = MakeTR(ref s.a);
// Only field a is for sure valid. There is nothing requiring b to be retained. I don't see how |
I think this is the point. You seem to be taking it for granted that taking the address of a local is sufficient to keep the local, and all its state, rooted for the duration of the method. Is that the case though and does the codegen guarantee that? If yes then I think everything we need falls out from that. |
Closing the loop.
Yes. The intuition here is that when taking an address of a stack local, it is possible to pass that out of the current scope. This means that unless the JIT can guarantee that the scope taking the ref struct S
{
public string a;
public string b;
}
S s = default;
var tr = NonInlinableFunction(ref s)
// All of 's' must be kept because the JIT can't see into the non-inlinable assembly and confirm what is or isn't used.
S s = default;
var tr = InlinableFunction(ref s)
// Potential optimization because the JIT can see all uses of the fields. |
Closing out as we verified the behavior here. Thanks everyone! |
Consider the following code:
Is the access of
copy.Field
safe?The variable
local
is unused after the__makeref
call and should be eligible for collection. It's unclear if theTypedReference
instance is sufficient to GC root the entirestruct
. Consider the structure ofTypedReference
is effectively:So in this case
tr
is effectively holding aref byte
pointing to the firstbyte
ofS.Age
. If a GC occurs before the__refvalue
call is it possible thatlocal.Field
gets collected? Given that theTypedReference
in this case points to astruct
local, and not field within a reference type, it's unclear if that is sufficient to GC root the entirestruct
contents.Wanted to get a bit of clarity on this as it's come up a few times in customer chats and is related to the upcoming fast reflection work.
The text was updated successfully, but these errors were encountered: