Skip to content

JIT does not inline operators against generic math #68890

@hez2010

Description

@hez2010

Description

I'm implementing a generic Point<T> struct with below code:

record struct Point<T>(T X, T Y) : INumberBase<Point<T>> where T : INumberBase<T>
{
    public static Point<T> One => new(T.One, T.One);
    public static Point<T> Zero => new(T.Zero, T.Zero);
    public static Point<T> AdditiveIdentity => new(T.Zero, T.Zero);
    public static Point<T> MultiplicativeIdentity => new(T.One, T.One);
    public static Point<T> operator checked +(Point<T> left, Point<T> right) => new (checked(left.X + right.X), checked(left.Y + right.Y));
    public static Point<T> operator checked -(Point<T> value) => new(checked(-value.X), checked(-value.Y));
    public static Point<T> operator checked -(Point<T> left, Point<T> right) => new(checked(left.X - right.X), checked(left.Y - right.Y));
    public static Point<T> operator checked ++(Point<T> value) => checked(value + One);
    public static Point<T> operator checked --(Point<T> value) => checked(value - One);
    public static Point<T> operator checked *(Point<T> left, Point<T> right) => new(checked(left.X * right.X), checked(left.Y * right.Y));
    public static Point<T> operator checked /(Point<T> left, Point<T> right) => new(checked(left.X / right.X), checked(left.Y / right.Y));
    public string ToString(string? format, IFormatProvider? formatProvider) => $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)})";
    public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) => throw new NotImplementedException();
    public static Point<T> operator +(Point<T> value) => value;
    public static Point<T> operator +(Point<T> left, Point<T> right) => new(left.X + right.X, left.Y + right.Y);
    public static Point<T> operator -(Point<T> value) => new(-value.X, -value.Y);
    public static Point<T> operator -(Point<T> left, Point<T> right) => new(left.X - right.X, left.Y - right.Y);
    public static Point<T> operator ++(Point<T> value) => value + One;
    public static Point<T> operator --(Point<T> value) => value - One;
    public static Point<T> operator *(Point<T> left, Point<T> right) => new(left.X * right.X, left.Y * right.Y);
    public static Point<T> operator /(Point<T> left, Point<T> right) => new(left.X / right.X, left.Y / right.Y);
}

But when I test it with a simple snippet:

var p1 = new Point<int>(3, 4);
var p2 = new Point<int>(5, 6);
return (p1 + p2).X + (p1 + p2).Y;

The JIT fails to inline operator+ thus it produces extremely bad codegen:

push      rsi
sub       rsp,40
mov       dword ptr [rsp+30],3
mov       dword ptr [rsp+34],4
mov       dword ptr [rsp+28],5
mov       dword ptr [rsp+2C],6
mov       rcx,[rsp+30]
mov       rdx,[rsp+28]
call      qword ptr [7FFF8E0B7CA8]
mov       [rsp+38],rax
mov       esi,[rsp+38]
mov       dword ptr [rsp+30],3
mov       dword ptr [rsp+34],4
mov       dword ptr [rsp+28],5
mov       dword ptr [rsp+2C],6
mov       rcx,[rsp+30]
mov       rdx,[rsp+28]
call      qword ptr [7FFF8E0B7CA8]
mov       [rsp+38],rax
mov       eax,esi
add       eax,[rsp+3C]
add       rsp,40
pop       rsi
ret

Only if I put [MethodImpl(MethodImplOptions.AggressiveInlining)] will the codegen be optimal:

mov       eax,12
ret

And the performance difference:

Method Mean Error StdDev Code Size
Test_NoInlining 23.12 ns 0.499 ns 0.820 ns 172 B
Test_Inlining 0.0099 ns 0.0145 ns 0.0332 ns 6 B

It's 2335x slower compared to the inlined one! Note that turning on PGO does not help in this case.
Operators are usually extremely hot path so I expect them to be inlined.

IL for operator+:

IL_0000:	ldarga.s	00 
IL_0002:	call	Point <T>.get_X ()
IL_0007:	ldarga.s	01 
IL_0009:	call	Point <T>.get_X ()
IL_000E:	constrained.	Point <T>.T
IL_0014:	call	IAdditionOperators <T, T, T>.op_Addition (T, T)
IL_0019:	ldarga.s	00 
IL_001B:	call	Point <T>.get_Y ()
IL_0020:	ldarga.s	01 
IL_0022:	call	Point <T>.get_Y ()
IL_0027:	constrained.	Point <T>.T
IL_002D:	call	IAdditionOperators <T, T, T>.op_Addition (T, T)
IL_0032:	newobj	Point <T>..ctor
IL_0037:	ret	

Configuration

.NET 7: build from master

category:cq
theme:inlining
skill-level:intermediate
cost:medium
impact:medium

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions