Skip to content

Conversation

@EgorBo
Copy link
Member

@EgorBo EgorBo commented Jan 2, 2026

Motivation:

  1. Set written rules for the JIT optimizer when it can and when it cannot optimize boxings for structs JIT: boxing shouldn't be removed for escaping managed references #122099. If we don't set the rules, the optimization that the JIT does is only valid when the method over the boxed struct is inlined and JIT's escape analysis clearly sees that nothing escapes anywhere. If we conservatively make it so, it will be a noticeable performance regression over previous releases.
  2. Allow JIT to perform a free inter-procedural escape analysis by only looking at call's signature, for an example:
Span<int> buffer = new int[10];
Consume(buffer); 

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:

void Case1(ref Span<int> X)
{
    Span<int> buffer = new int[10];
    Consume(buffer, ref X); // buffer cannot be stack-allocated as it might escape through X
}

ref int Case2()
{
    Span<int> buffer = new int[10];
    return ref Consume(buffer); // buffer cannot be stack-allocated as it might escape through return ref
}

ByrefLikeType Case3()
{
    Span<int> buffer = new int[10];
    return Consume(buffer); // buffer cannot be stack-allocated as it might escape through return ByrefLikeType
}

// ByrefLikeTypeWithRefRef can not be declared today
void Case4(ByrefLikeTypeWithRefRef p)
{
    Span<int> buffer = new int[10];
    return Consume(buffer, p); // buffer cannot be stack-allocated as it might escape through p parameter
}

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:

  • If we call it UB, should we make 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.

@EgorBo EgorBo marked this pull request as ready for review January 2, 2026 16:58
Copilot AI review requested due to automatic review settings January 2, 2026 16:58
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jan 2, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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

EgorBo and others added 3 commits January 2, 2026 18:02
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@jkotas jkotas added area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jan 2, 2026
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.


### 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:
Copy link
Member

Choose a reason for hiding this comment

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

What does "derived" mean exactly? The existing uses of "derived" in the ECMA spec are about inheritance.

Copy link
Member Author

Choose a reason for hiding this comment

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

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?

Copy link
Member

@jkotas jkotas Jan 2, 2026

Choose a reason for hiding this comment

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

array elements

Marshal.UnsafeAddrOfPinnedArrayElement does exactly that. I think this needs to be more limited.

@jkotas
Copy link
Member

jkotas commented Jan 2, 2026

If we call it UB, should we make warning CS9085: This ref-assigns 'field' to 'p' but 'field' has a narrower escape scope than 'p'. an error?

Does this warning have false positives - does it fire on correct (unsafe) code?

@EgorBo
Copy link
Member Author

EgorBo commented Jan 2, 2026

If we call it UB, should we make warning CS9085: This ref-assigns 'field' to 'p' but 'field' has a narrower escape scope than 'p'. an error?

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 unsafe keyword here, it's probably fine to keep it as is?

@jkotas
Copy link
Member

jkotas commented Jan 2, 2026

Hard to tell, here is the minimal repro for that warning to show up:

It shows up for a code like this that is not obviously invalid:

struct MyStruct
{
    int f;

    unsafe ref int M(ref int x)
    {
        x = ref f;
        return ref x;
    }
}

given it already requires unsafe keyword here, it's probably fine to keep it as is?

Right, my guess it was the idea for keeping it as a warning - to have an escape hatch for false positives.

@tfenise
Copy link
Contributor

tfenise commented Jan 3, 2026

/// <summary>
/// Writes a value of type <typeparamref name="T"/> to the given location.
/// </summary>
[Intrinsic]
[NonVersionable]
[CLSCompliant(false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Write<T>(void* destination, T value)
where T : allows ref struct
{
*(T*)destination = value;
}

So now Unsafe.Write<Span<byte>> is unconditionally undefined behavior, as the Span<byte> escapes in a disallowed way? Unsafe.Write<T> shouldn't allow T : allows ref struct?

@EgorBo
Copy link
Member Author

EgorBo commented Jan 3, 2026

So now Unsafe.Write<Span<byte>> is unconditionally undefined behavior, as the Span<byte> escapes in a disallowed way? Unsafe.Write<T> shouldn't allow T : allows ref struct?

Interesting question. I'm not sure why the where T : allows ref struct constraint was added for Write specifically in the first place, probably just because it was added elsewhere too, at this point removing it is a breaking change. There are probably valid use-cases for it being used with ref structs (esp since ref structs aren't required to have ref fields).

Perhaps, the UB part needs to be re-phrased. "If you use unsafe and you know what you do - it's fine" 🙂

@jkotas
Copy link
Member

jkotas commented Jan 3, 2026

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants