Skip to content

JIT: Transform SELECT(x < 0, C - 1, C) to SAR(x, 31) + C#125549

Open
BoyBaykiller wants to merge 6 commits intodotnet:mainfrom
BoyBaykiller:transform-select-to-sar-add
Open

JIT: Transform SELECT(x < 0, C - 1, C) to SAR(x, 31) + C#125549
BoyBaykiller wants to merge 6 commits intodotnet:mainfrom
BoyBaykiller:transform-select-to-sar-add

Conversation

@BoyBaykiller
Copy link
Contributor

@BoyBaykiller BoyBaykiller commented Mar 14, 2026

This pattern saves 2 registers (or 1 when the immed can't be contained on arm) and is a code size reduction.
In the special case of C = 0 it becomes a single sar.

Examples:

void Example1(int relop1)
{
    int x = (relop1 < 0) ? 4 : 5;
    Consume(x);
}
G_M000_IG02:                ;; offset=0x0000
       mov      ecx, 4
       mov      eax, 5
       test     edx, edx
       cmovge   ecx, eax

G_M000_IG03:                ;; offset=0x000F
       tail.jmp [Consume(int)]
; Total bytes of code: 21
G_M10187_IG02:  ;; offset=0x0000
       sar      edx, 31
       lea      ecx, [rdx+0x05]

G_M10187_IG03:  ;; offset=0x0006
       tail.jmp [Consume(int)]
; Total bytes of code: 12

int Example2(int result)
{
    return (result < 0) ? int.MaxValue : int.MinValue;
}
G_M000_IG02:                ;; offset=0x0000
       mov      eax, 0x7FFFFFFF
       mov      ecx, 0xFFFFFFFF80000000
       test     edx, edx
       cmovge   eax, ecx

G_M000_IG03:                ;; offset=0x000F
       ret      
; Total bytes of code: 16
G_M35951_IG02:  ;; offset=0x0000
       mov      eax, edx
       sar      eax, 31
       add      eax, 0xFFFFFFFF80000000

G_M35951_IG03:  ;; offset=0x000A
       ret      
; Total bytes of code: 11

@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 14, 2026
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Mar 14, 2026
@jakobbotsch
Copy link
Member

@copilot Use the performance benchmark skill to create micro benchmarks for this pattern

@jakobbotsch
Copy link
Member

@EgorBot -linux_amd -osx_arm64

   using BenchmarkDotNet.Attributes;
   using BenchmarkDotNet.Running;
   using System.Runtime.CompilerServices;
   
   BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);
   
   public class Bench
   {
       private int[] _values = default!;
   
       [GlobalSetup]
       public void Setup()
       {
           var rng = new Random(42);
           _values = new int[1024];
           for (int i = 0; i < _values.Length; i++)
               _values[i] = rng.Next(-1000, 1000);
       }
   
       [Benchmark]
       public int SelectLtZero_ConstOffset()
       {
           int sum = 0;
           int[] arr = _values;
           for (int i = 0; i < arr.Length; i++)
               sum += LtZero_Offset(arr[i]);
           return sum;
       }
   
       [Benchmark]
       public int SelectGeZero_ConstOffset()
       {
           int sum = 0;
           int[] arr = _values;
           for (int i = 0; i < arr.Length; i++)
               sum += GeZero_Offset(arr[i]);
           return sum;
       }
   
       [Benchmark]
       public int SelectLtZero_ZeroBase()
       {
           int sum = 0;
           int[] arr = _values;
           for (int i = 0; i < arr.Length; i++)
               sum += LtZero_ZeroBase(arr[i]);
           return sum;
       }
   
       [MethodImpl(MethodImplOptions.NoInlining)]
       private static int LtZero_Offset(int x) => x < 0 ? 41 : 42;
   
       [MethodImpl(MethodImplOptions.NoInlining)]
       private static int GeZero_Offset(int x) => x >= 0 ? 42 : 41;
   
       [MethodImpl(MethodImplOptions.NoInlining)]
       private static int LtZero_ZeroBase(int x) => x < 0 ? -1 : 0;
   }

* remove nodes at once
* remove duplicate MakeSrcContained
@BoyBaykiller BoyBaykiller marked this pull request as ready for review March 15, 2026 23:46
@BoyBaykiller
Copy link
Contributor Author

We have transformations on SELECT in If Conversion as well as Lower.
Having them in If Conversion means it doesn't catch potential SELECTs generated before.
Having them in Lower makes it clunky.

A dedicated phase after If Conversion would solve that. But I understood you that it's a hard sell and not a big deal the way it's currently. Just leaving this here for the record.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants