dotnet / dotnet/runtime

JIT: (bug) signed Log2 intrinsic drops ArgumentOutOfRangeException when the result is unused

Open
#133,965 1 comment 0 reactions 0 assignees View on GitHub
area-CodeGen-coreclr
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

For signed `int.Log2` and `long.Log2`, the imported throwing fallback can be deleted when the intrinsic result is dead, so negative inputs return normally instead of throwing.

### Minimal Repro

```csharp
using System;
using System.Runtime.CompilerServices;

public class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static int Neg() => -1;

[MethodImpl(MethodImplOptions.NoInlining)]
public static long NegL() => -1;

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void Test(int x)
{
int unused = int.Log2(x);
}

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void TestLong(long x)
{
long unused = long.Log2(x);
}

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static int TestUsed(int x)
{
return int.Log2(x);
}

public static void Main()
{
try { Test(Neg()); Console.WriteLine("Test: No exception"); }
catch (ArgumentOutOfRangeException) { Console.WriteLine("Test: ArgumentOutOfRangeException"); }

try { TestLong(NegL()); Console.WriteLine("TestLong: No exception"); }
catch (ArgumentOutOfRangeException) { Console.WriteLine("TestLong: ArgumentOutOfRangeException"); }

try { TestUsed(Neg()); Console.WriteLine("TestUsed: No exception"); }
catch (ArgumentOutOfRangeException) { Console.WriteLine("TestUsed: ArgumentOutOfRangeException"); }
}
}
```

### Expected

```
Test: ArgumentOutOfRangeException
TestLong: ArgumentOutOfRangeException
TestUsed: ArgumentOutOfRangeException
```

### Actual

```
Test: No exception
TestLong: No exception
TestUsed: ArgumentOutOfRangeException
```

### Notes

The signed `Log2` fallback is a `GT_INTRINSIC` with `GTF_CALL`, but `GT_INTRINSIC` is not treated as throwing/side-effecting by the relevant side-effect checks.
Early liveness therefore deletes the dead store containing the fallback; `Test` and `TestLong` become a bare `ret`.
This is a regression in main; .NET 10 throws as expected.

Contributor guide

Open the contributing guide

Research direction

Start with the Test, TestLong, and TestUsed entry points in the minimal repro and confirm the differing behavior for unused and used signed Log2 results. Then inspect the JIT side-effect checks for GT_INTRINSIC nodes with GTF_CALL. Done means negative int.Log2 and long.Log2 inputs throw ArgumentOutOfRangeException even when the result is unused, while the used case remains correct.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.