JIT: (bug) signed Log2 intrinsic drops ArgumentOutOfRangeException when the result is unused
- 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
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