Skip to content
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: Fix constrained call dereferences happening too early #86638

Merged
merged 3 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,21 @@ var_types Compiler::impImportCall(OPCODE opcode,
}

//-------------------------------------------------------------------------
// The main group of arguments
// The main group of arguments, and the this pointer.

// 'this' is pushed on the IL stack before all call args, but if this is a
// constrained call 'this' is a byref that may need to be dereferenced.
// That dereference should happen _after_ all args, so we need to spill
// them if they can interfere.
bool hasThis;
hasThis = ((mflags & CORINFO_FLG_STATIC) == 0) && ((sig->callConv & CORINFO_CALLCONV_EXPLICITTHIS) == 0) &&
((opcode != CEE_NEWOBJ) || (newobjThis != nullptr));

if (hasThis && (constraintCallThisTransform == CORINFO_DEREF_THIS))
{
impSpillSideEffects(false, CHECK_SPILL_ALL DEBUGARG(
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't recall exactly what a "global effect" is in this context. Could the same issue happen if the this pointer was from a loaded field rather than a local?

Copy link
Member Author

@jakobbotsch jakobbotsch Apr 8, 2024

Choose a reason for hiding this comment

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

spillGlobEffects here means to also spill nodes that read heap memory, in addition to all other side effects. We don't need to spill those because inserting an indirection doesn't modify global state.

The same issue can happen if the pointer is from a loaded field, but the spill here is still sufficient (anything that can modify global memory will be spilled even without spillGlobEffects).

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

"constrained call requires dereference for 'this' right before call"));
}

impPopCallArgs(sig, call->AsCall());
if (extraArg.Node != nullptr)
Expand All @@ -904,8 +918,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
//-------------------------------------------------------------------------
// The "this" pointer

if (((mflags & CORINFO_FLG_STATIC) == 0) && ((sig->callConv & CORINFO_CALLCONV_EXPLICITTHIS) == 0) &&
!((opcode == CEE_NEWOBJ) && (newobjThis == nullptr)))
if (hasThis)
{
GenTree* obj;

Expand Down
60 changes: 60 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// Reference source for Runtime_73615.il.

using InlineIL;
using System;
using System.Runtime.CompilerServices;
using Xunit;

public class Runtime_73615
{
[Fact]
public static int TestEntryPoint()
{
Foo(new C(101));

if (Result == 100)
{
Console.WriteLine("PASS");
}
else
{
Console.WriteLine("FAIL: Got result {0}", Result);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the autogenerated harness will already display failure information.


return Result;
}

public static int Result;

[MethodImpl(MethodImplOptions.NoInlining)]
private static void Foo(C arg)
{
IL.Emit.Ldarga(nameof(arg));
IL.Emit.Ldarga(nameof(arg));
IL.Emit.Call(new MethodRef(typeof(Runtime_73615), nameof(Bar)));
IL.Emit.Constrained<C>();
IL.Emit.Callvirt(new MethodRef(typeof(C), nameof(arg.Baz)));
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int Bar(ref C o)
{
o = new C(100);
return 0;
}

public class C
{
public C(int result) => Result = result;
public int Result;

[MethodImpl(MethodImplOptions.NoInlining)]
public void Baz(int arg)
{
Runtime_73615.Result = Result;
}
}
}
105 changes: 105 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.il
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

.assembly extern System.Runtime { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) }
.assembly extern xunit.core { }
.assembly extern System.Console { }

.assembly Runtime_73615 { }

.class public auto ansi beforefieldinit Runtime_73615
extends [System.Runtime]System.Object
{
.class auto ansi nested public beforefieldinit C
extends [System.Runtime]System.Object
{
.field public int32 Result
.method public hidebysig specialname rtspecialname
instance void .ctor(int32 result) cil managed
{
// Code size 14 (0xe)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: ldarg.0
IL_0007: ldarg.1
IL_0008: stfld int32 Runtime_73615/C::Result
IL_000d: ret
} // end of method C::.ctor

.method public hidebysig instance void
Baz(int32 arg) cil managed noinlining
{
// Code size 12 (0xc)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld int32 Runtime_73615/C::Result
IL_0006: stsfld int32 Runtime_73615::Result
IL_000b: ret
} // end of method C::Baz

} // end of class C

.field public static int32 Result
.method public hidebysig static int32 Main() cil managed
{
.custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = ( 01 00 00 00 )
.entrypoint
// Code size 59 (0x3b)
.maxstack 8
IL_0000: ldc.i4.s 101
IL_0002: newobj instance void Runtime_73615/C::.ctor(int32)
IL_0007: call void Runtime_73615::Foo(class Runtime_73615/C)
IL_000c: ldsfld int32 Runtime_73615::Result
IL_0011: ldc.i4.s 100
IL_0013: bne.un.s IL_0021

IL_0015: ldstr "PASS"
IL_001a: call void [System.Console]System.Console::WriteLine(string)
IL_001f: br.s IL_0035

IL_0021: ldstr "FAIL: Got result {0}"
IL_0026: ldsfld int32 Runtime_73615::Result
IL_002b: box [System.Runtime]System.Int32
IL_0030: call void [System.Console]System.Console::WriteLine(string,
object)
IL_0035: ldsfld int32 Runtime_73615::Result
IL_003a: ret
} // end of method Runtime_73615::TestEntryPoint

.method private hidebysig static void Foo(class Runtime_73615/C arg) cil managed noinlining
{
// Code size 21 (0x15)
.maxstack 2
IL_0000: ldarga.s arg
IL_0002: ldarga.s arg
IL_0004: call int32 Runtime_73615::Bar(class Runtime_73615/C&)
IL_0009: constrained. Runtime_73615/C
IL_000f: callvirt instance void Runtime_73615/C::Baz(int32)
IL_0014: ret
} // end of method Runtime_73615::Foo

.method private hidebysig static int32
Bar(class Runtime_73615/C& o) cil managed noinlining
{
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.s 100
IL_0003: newobj instance void Runtime_73615/C::.ctor(int32)
IL_0008: stind.ref
IL_0009: ldc.i4.0
IL_000a: ret
} // end of method Runtime_73615::Bar

.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: ret
} // end of method Runtime_73615::.ctor

} // end of class Runtime_73615
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.IL">
<PropertyGroup>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).il" />
</ItemGroup>
</Project>
Loading