Add III.1.7 "Managed pointers exposing parameters outside of the method scope" to Ecma-335-Augments.md#122821
Add III.1.7 "Managed pointers exposing parameters outside of the method scope" to Ecma-335-Augments.md#122821EgorBo wants to merge 5 commits intodotnet:mainfrom
Conversation
…od scope" to Ecma-335-Augments.md
There was a problem hiding this comment.
Pull request overview
This PR adds a new specification section III.1.7.7 "Managed pointers exposing parameters outside of the method scope" to the Ecma-335 augments document. The goal is to formalize rules for when byrefs derived from method parameters can escape a function's scope, enabling JIT compiler optimizations such as boxing optimizations and inter-procedural escape analysis.
Key changes:
- Defines two permitted ways for byrefs from parameters to escape: explicit returns and writes through byrefs to byref-like types
- Declares undefined behavior for any other escape attempts
- Provides examples to clarify when parameters can and cannot expose values
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
|
|
||
| ### III.1.7.7 | ||
| Add a new paragraph "III.1.7.7 Managed pointers exposing parameters outside of the method scope" under section "III.1.7 Restrictions on CIL code sequences" | ||
| Byrefs derived from method parameters can escape from a function only in the following ways: |
There was a problem hiding this comment.
What does "derived" mean exactly? The existing uses of "derived" in the ECMA spec are about inheritance.
There was a problem hiding this comment.
When we take a byref from a parameter directly or from any of its fields or array elements.
Byrefs obtained from parameters directly or indirectly (byrefs to their fields or array elements)
is it better?
There was a problem hiding this comment.
array elements
Marshal.UnsafeAddrOfPinnedArrayElement does exactly that. I think this needs to be more limited.
Does this warning have false positives - does it fire on correct (unsafe) code? |
Hard to tell, here is the minimal repro for that warning to show up: unsafe void M(ref int x)
{
int y = 0;
x = ref y; // warning CS9085: This ref-assigns 'y' to 'x' but 'y' has a narrower escape scope than 'x'.
}given it already requires |
It shows up for a code like this that is not obviously invalid:
Right, my guess it was the idea for keeping it as a warning - to have an escape hatch for false positives. |
|
So now |
Interesting question. I'm not sure why the Perhaps, the UB part needs to be re-phrased. "If you use unsafe and you know what you do - it's fine" 🙂 |
We need better description of what is valid unsafe code vs. invalid unsafe code than this. If we are not able to come up with one, I would rather revert the problematic JIT optimization. |
We never added the optimization... it was partially implemented in #112543 which we gave up on. |
I believe that the issue described in #122099 that motivated this discussion is in the product currently. |
|
Right, @AndyAyersMS we talk about the existing optimization in public void Foo(MyStruct ms)
{
((IMyStruct)ms).DoWork();
//
// mov r11, 0x7FFDB60500E8
// cmp dword ptr [rcx], ecx
// tail.jmp [r11]IMyStruct:DoWork():this
}
public interface IMyStruct
{
void DoWork();
}
public class MyStruct : IMyStruct
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void DoWork()
{
// escapes itself in some odd way
}
}today we happily remove the |
|
Sounds like either way we must defer the box removal to escape analysis. I think that should be relatively straightforward: in I can put up a PR for this. |
It's definitely better than not doing anything, although, I expect some noticeable regressions from cases where we can't inline (e.g. too big) or EA gives up due to some non-inlined call etc. I still would prefer defining the behavior and allow ourselves to make assumptions based on call signature alone. We've had this behavior in |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
I do not think the latest revision is going to allow these boxing optimizations. Could you please update the PR description if you agree? |
I am probably misreading it, but can you clarify why we can't possibly do it for: static void Foo()
{
MyStruct ms = new();
((IDisposable)ms).Dispose(); // can we legally remove this boxing without inlining or inter-procedural analysis?
}
public struct MyStruct : IDisposable
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void Dispose()
{
// non-UB code
}
} |
|
Ah ok. "Obtained from method parameters" needs further clarification. For example: Is this UB with this rule or not? (I think it should remain valid - it looks like a real-world code one can write.) |
Motivation:
given the
void Consume(Span<int>)signature, JIT can be sure that the buffer never escapes anywhere without looking into actual implementation. We expect this to unlock the potential of the current Escape Analysis and handle a lot more cases.Another examples:
Based on the discussion with @jakobbotsch @jkotas. Let me know if the wording needs to be improved.
PS: "multiple levels of byref fields" is not something that can be represented today in C#, but we leave a room for it.
Open questions:
warning CS9085: This ref-assigns 'field' to 'p' but 'field' has a narrower escape scope than 'p'.an error? UPD: given it requires unsafe and there are, possibly, valid scenarios it's fine to leave it as is.