-
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
JIT struct work planned for .NET 9 #93105
Comments
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsThis issue captures the planned work items for .NET 9 with respect to struct improvements. This list is expected to change throughout the release cycle according to ongoing planning and discussions, with possible additions and subtractions to the scope. Expanding the scope of physical promotion.NET 8 added physical promotion, which removes many of the limitations of the existing promotion pass. Physical promotion has the following pros over the regular promotion pass:
Long term, we would like physical promotion to replace the regular promotion pass entirely. We do NOT expect this to happen in .NET 9. However, we do expect to make progress towards this goal in .NET 9. ABI handlingThe main limitation that stops physical promotion from replacing regular promotion is currently that it lacks the support around ABI boundaries that regular promotion has. This comes out of the fact that our multireg support is tied very directly into the existing promotion mechanism.
The Reducing scope of dependent promotionWe also expect to switch some cases where we can predict dependent promotion to be handled by physical promotion instead. This is generally expected to have good CQ benefits as it allows fields to stay in registers.
To accomplish the first item we will likely reorder regular promotion and local morph. This means introducing another walk over locals somewhere after regular promotion to replace Struct copy propagationThe JIT has to create a temporary when constructing structs on the off chance that the constructor observes address of This work item is about introducing a pass to get rid of copies in the common case where we inlined and proved them to be unnecessary. Miscellaneous work items
|
A couple of user examples where dependent promotion harms CQ: using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
public static class Tests
{
public static MyVec2 TestAdd1(MyVec2 v1, MyVec2 v2) => MyVec2.Add1(v1, v2);
public static MyVec2 TestAdd2(MyVec2 v1, MyVec2 v2) => MyVec2.Add2(v1, v2);
}
public struct MyVec2
{
public double X, Y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MyVec2 Add1(MyVec2 left, MyVec2 right)
{
var tmp = Unsafe.As<MyVec2, Vector128<double>>(ref left) + Unsafe.As<MyVec2, Vector128<double>>(ref right);
return Unsafe.As<Vector128<double>, MyVec2>(ref tmp);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MyVec2 Add2(MyVec2 left, MyVec2 right)
{
var tmp = Unsafe.BitCast<MyVec2, Vector128<double>>(left) + Unsafe.BitCast<MyVec2, Vector128<double>>(right);
return Unsafe.BitCast<Vector128<double>, MyVec2>(tmp);
}
} CQ of using System;
using System.Runtime.CompilerServices;
using System.Numerics;
using System.Runtime.Intrinsics;
public static class Tests
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static Vector2<double> ByRefOperatorAdd(Vector2<double> v1, Vector2<double> v2)
{
return v1 + v2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static Vector2<double> ByRefMethodAdd(Vector2<double> v1, Vector2<double> v2)
{
return Vector2<double>.Add(in v1, in v2);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static Vector2<double> ByValMethodAdd(Vector2<double> v1, Vector2<double> v2)
{
return Vector2<double>.Add2(v1, v2);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static Vector2<double> ByRefMethodAdd2(Vector2<double> v1, Vector2<double> v2)
{
return Vector2<double>.Add3(in v1, in v2);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static Vector2<double> ByValMethodAdd2(Vector2<double> v1, Vector2<double> v2)
{
return Vector2<double>.Add4(v1, v2);
}
}
public static class Extensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static ref readonly Vector128<T> AsVector128<T>(in this Vector2<T> vector) where T : unmanaged, INumber<T>
{
return ref Unsafe.As<Vector2<T>, Vector128<T>>(ref Unsafe.AsRef(in vector));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static ref readonly Vector2<T> AsVector2<T>(in this Vector128<T> vector) where T : unmanaged, INumber<T>
{
return ref Unsafe.As<Vector128<T>, Vector2<T>>(ref Unsafe.AsRef(in vector));
}
}
public readonly struct Vector2<T> where T : unmanaged, INumber<T>
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static Vector2<T> operator +(in Vector2<T> left, in Vector2<T> right) => Add(in left, in right);
public T X
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get;
}
public T Y
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get;
}
public Vector2(T x, T y)
{
X = x;
Y = y;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static Vector2<T> Add(in Vector2<T> left, in Vector2<T> right)
{
unsafe
{
// Total size = 128 bits
if (sizeof(Vector2<T>) == 16)
{
if (Vector128<T>.IsSupported && Vector128.IsHardwareAccelerated)
return (left.AsVector128() + right.AsVector128()).AsVector2();
}
}
return new(left.X + right.X, left.Y + right.Y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static Vector2<T> Add2(Vector2<T> left, Vector2<T> right)
{
unsafe
{
// Total size = 128 bits
if (sizeof(Vector2<T>) == 16)
{
if (Vector128<T>.IsSupported && Vector128.IsHardwareAccelerated)
return (left.AsVector128() + right.AsVector128()).AsVector2();
}
}
return new(left.X + right.X, left.Y + right.Y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static Vector2<T> Add3(in Vector2<T> left, in Vector2<T> right)
{
return new(left.X + right.X, left.Y + right.Y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static Vector2<T> Add4(Vector2<T> left, Vector2<T> right)
{
return new(left.X + right.X, left.Y + right.Y);
}
}
Both examples produce identical codegen between the by-ref/by-val versions under |
Closing as .NET 9 work is complete. |
This issue captures the planned work items for .NET 9 with respect to struct improvements. This list is expected to change throughout the release cycle according to ongoing planning and discussions, with possible additions and subtractions to the scope.
Expanding the scope of physical promotion
.NET 8 added physical promotion, which removes many of the limitations of the existing promotion pass. Physical promotion has the following pros over the regular promotion pass:
Long term, we would like physical promotion to replace the regular promotion pass entirely. We do NOT expect this to happen in .NET 9. However, we do expect to make progress towards this goal in .NET 9.
ABI handling
The main limitation that stops physical promotion from replacing regular promotion is currently that it lacks the support around ABI boundaries that regular promotion has. This comes out of the fact that our multireg support is tied very directly into the existing promotion mechanism.
Thus, we expect to work on some of the following items in .NET 9:
The
FIELD_LIST
items are expected to be easier than the last two items (this is related to the fact that we do not have a good way of representing multiply-defined things in JIT IR).Reducing scope of dependent promotion
We also expect to switch some cases where we can predict dependent promotion to be handled by physical promotion instead. This is generally expected to have good CQ benefits as it allows fields to stay in registers.
Miscellaneous work items
LCL_ADDR
nodes during local morph #102808Moved out of .NET 9
Struct copy propagation
The JIT has to create a temporary when constructing structs on the off chance that the constructor observes address of
this
. However, practically no struct constructor depends onthis
, and we are typically able to prove this after inlining. In many cases, however, we still end up with subpar CQ due to the copy.This work item is about introducing a pass to get rid of copies in the common case where we inlined and proved them to be unnecessary.
The text was updated successfully, but these errors were encountered: