Repository: dotnet/runtime
Pull request #114900: [RISC-V] Extend register for slti
Author: login: tomeksowi
Labels: name: area-CodeGen-coreclr
description: CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
name: community-contribution
description: Indicates that the PR has been added by a community member
name: arch-riscv
description: Related to the RISC-V architecture
Created: 2025-04-22T10:22:49Z | Merged: 2025-04-27T17:48:12Z

Sometimes the register for `slti` may be not extended. For now patch like other comparisons -- by sign-extending always.

Part of #84834, cc @dotnet/samsung


Diff:
diff --git a/src/coreclr/jit/codegenriscv64.cpp b/src/coreclr/jit/codegenriscv64.cpp
index 9fbe0905e102ee..167d7a16def46b 100644
--- a/src/coreclr/jit/codegenriscv64.cpp
+++ b/src/coreclr/jit/codegenriscv64.cpp
@@ -3208,22 +3208,23 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree)
             switch (cmpSize)
             {
                 case EA_4BYTE:
+                {
+                    regNumber tmpRegOp1 = internalRegisters.GetSingle(tree);
+                    assert(regOp1 != tmpRegOp1);
                     if (isUnsigned)
                     {
                         imm = static_cast<uint32_t>(imm);
-
-                        regNumber tmpRegOp1 = internalRegisters.GetSingle(tree);
-                        assert(regOp1 != tmpRegOp1);
-
                         emit->emitIns_R_R_I(INS_slli, EA_8BYTE, tmpRegOp1, regOp1, 32);
                         emit->emitIns_R_R_I(INS_srli, EA_8BYTE, tmpRegOp1, tmpRegOp1, 32);
-                        regOp1 = tmpRegOp1;
                     }
                     else
                     {
                         imm = static_cast<int32_t>(imm);
+                        emit->emitIns_R_R(INS_sext_w, EA_8BYTE, tmpRegOp1, regOp1);
                     }
+                    regOp1 = tmpRegOp1;
                     break;
+                }
                 case EA_8BYTE:
                     break;
                 default:
diff --git a/src/coreclr/jit/lsrariscv64.cpp b/src/coreclr/jit/lsrariscv64.cpp
index 71def9a584acd7..4476bfb09af9cc 100644
--- a/src/coreclr/jit/lsrariscv64.cpp
+++ b/src/coreclr/jit/lsrariscv64.cpp
@@ -445,17 +445,8 @@ int LinearScan::BuildNode(GenTree* tree)
             if (!varTypeIsFloating(op1Type))
             {
                 emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type));
-                if (tree->gtGetOp2()->isContainedIntOrIImmed())
-                {
-                    bool isUnsigned = (tree->gtFlags & GTF_UNSIGNED) != 0;
-                    if (cmpSize == EA_4BYTE && isUnsigned)
-                        buildInternalIntRegisterDefForNode(tree);
-                }
-                else
-                {
-                    if (cmpSize == EA_4BYTE)
-                        buildInternalIntRegisterDefForNode(tree);
-                }
+                if (cmpSize == EA_4BYTE)
+                    buildInternalIntRegisterDefForNode(tree);
             }
             buildInternalRegisterUses();
         }
diff --git a/src/tests/JIT/Directed/Directed_2.csproj b/src/tests/JIT/Directed/Directed_2.csproj
index 3ac99ccb9e724c..96220b3159446b 100644
--- a/src/tests/JIT/Directed/Directed_2.csproj
+++ b/src/tests/JIT/Directed/Directed_2.csproj
@@ -4,6 +4,7 @@
     <MergedWrapperProjectReference Include="perffix/**/*.??proj" />
     <MergedWrapperProjectReference Include="PREFIX/**/*.??proj" />
     <MergedWrapperProjectReference Include="shift/**/*.??proj" />
+    <MergedWrapperProjectReference Include="compare/**/*.??proj" />
   </ItemGroup>
 
   <Import Project="$(TestSourceDir)MergedTestRunner.targets" />
diff --git a/src/tests/JIT/Directed/Directed_3.csproj b/src/tests/JIT/Directed/Directed_3.csproj
index 78ec52b6675c4c..22f7e7688b5fd7 100644
--- a/src/tests/JIT/Directed/Directed_3.csproj
+++ b/src/tests/JIT/Directed/Directed_3.csproj
@@ -11,6 +11,7 @@
     <MergedWrapperProjectReference Remove="perffix/**/*.??proj" />
     <MergedWrapperProjectReference Remove="PREFIX/**/*.??proj" />
     <MergedWrapperProjectReference Remove="shift/**/*.??proj" />
+    <MergedWrapperProjectReference Remove="compare/**/*.??proj" />
   </ItemGroup>
 
   <Import Project="$(TestSourceDir)MergedTestRunner.targets" />
diff --git a/src/tests/JIT/Directed/compare/int32.cs b/src/tests/JIT/Directed/compare/int32.cs
new file mode 100644
index 00000000000000..d631ad5071fe58
--- /dev/null
+++ b/src/tests/JIT/Directed/compare/int32.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+//
+
+using System.Runtime.CompilerServices;
+using Xunit;
+
+public class CompareTest
+{
+    [MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
+    public static bool Lt2((int, float) x) => (x.Item1 < 2);
+    
+    [MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
+    public static bool Gt2((int, float) x) => (x.Item1 > 2);
+
+    [MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
+    public static bool Le2((int, float) x) => (x.Item1 <= 2);
+    
+    [MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
+    public static bool Ge2((int, float) x) => (x.Item1 >= 2);
+
+    [MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
+    public static bool Eq0((int, float) x) => (x.Item1 == 0);
+    
+    [MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
+    public static bool Ne0((int, float) x) => (x.Item1 != 0);
+
+    [Fact]
+    public static void Test()
+    {
+        // The integer field of a struct passed in a register according to RISC-V floating-point calling convention is
+        // not extended. Comparisons on RISC-V, however, always operate on full registers.
+        // Note: reflection calls poison the undefined bits in runtime debug mode making it a better repro.
+        var type = typeof(CompareTest);
+        var args = new object[] {(2, 0f)};
+        Assert.False((bool)type.GetMethod("Lt2").Invoke(null, args));
+        Assert.False((bool)type.GetMethod("Gt2").Invoke(null, args));
+        Assert.True((bool)type.GetMethod("Le2").Invoke(null, args));
+        Assert.True((bool)type.GetMethod("Ge2").Invoke(null, args));
+        args = new object[] {(0, 0f)};
+        Assert.True((bool)type.GetMethod("Eq0").Invoke(null, args));
+        Assert.False((bool)type.GetMethod("Ne0").Invoke(null, args));
+    }
+}
diff --git a/src/tests/JIT/Directed/compare/int32.csproj b/src/tests/JIT/Directed/compare/int32.csproj
new file mode 100644
index 00000000000000..2929b8f6e68b31
--- /dev/null
+++ b/src/tests/JIT/Directed/compare/int32.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <CLRTestPriority>1</CLRTestPriority>
+  </PropertyGroup>
+  <PropertyGroup>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="int32.cs" />
+  </ItemGroup>
+</Project>


Reviews:
user: login: fuad1502
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T07:13:08Z
user: login: tomeksowi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T07:35:38Z
user: login: fuad1502
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T07:38:04Z
user: login: tomeksowi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T07:48:45Z
user: login: BruceForstall
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T18:16:58Z

Review comments:
diff_hunk: @@ -3208,22 +3208,23 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree)
             switch (cmpSize)
             {
                 case EA_4BYTE:
+                {
+                    regNumber tmpRegOp1 = internalRegisters.GetSingle(tree);
+                    assert(regOp1 != tmpRegOp1);
                     if (isUnsigned)
                     {
                         imm = static_cast<uint32_t>(imm);
-
-                        regNumber tmpRegOp1 = internalRegisters.GetSingle(tree);
-                        assert(regOp1 != tmpRegOp1);
-
                         emit->emitIns_R_R_I(INS_slli, EA_8BYTE, tmpRegOp1, regOp1, 32);
                         emit->emitIns_R_R_I(INS_srli, EA_8BYTE, tmpRegOp1, tmpRegOp1, 32);
path: src/coreclr/jit/codegenriscv64.cpp
user: login: fuad1502
body: This can be optimized with `add.uw tempRegOp1 regOp1 zero` now that we have `Zba`. But we can have that in a separate PR if you prefer it 👍
created_at: 2025-04-23T07:13:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3208,22 +3208,23 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree)
             switch (cmpSize)
             {
                 case EA_4BYTE:
+                {
+                    regNumber tmpRegOp1 = internalRegisters.GetSingle(tree);
+                    assert(regOp1 != tmpRegOp1);
                     if (isUnsigned)
                     {
                         imm = static_cast<uint32_t>(imm);
-
-                        regNumber tmpRegOp1 = internalRegisters.GetSingle(tree);
-                        assert(regOp1 != tmpRegOp1);
-
                         emit->emitIns_R_R_I(INS_slli, EA_8BYTE, tmpRegOp1, regOp1, 32);
                         emit->emitIns_R_R_I(INS_srli, EA_8BYTE, tmpRegOp1, tmpRegOp1, 32);
path: src/coreclr/jit/codegenriscv64.cpp
user: login: tomeksowi
body: It should be optimized with `sext.w`, sign-extending `uint` is natural on RISC-V and has advantages for `sltiu`. I'll do it another PR, it's not the only place we have patterns like this.
created_at: 2025-04-23T07:35:38Z
author_association: MEMBER
diff_hunk: @@ -3208,22 +3208,23 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree)
             switch (cmpSize)
             {
                 case EA_4BYTE:
+                {
+                    regNumber tmpRegOp1 = internalRegisters.GetSingle(tree);
+                    assert(regOp1 != tmpRegOp1);
                     if (isUnsigned)
                     {
                         imm = static_cast<uint32_t>(imm);
-
-                        regNumber tmpRegOp1 = internalRegisters.GetSingle(tree);
-                        assert(regOp1 != tmpRegOp1);
-
                         emit->emitIns_R_R_I(INS_slli, EA_8BYTE, tmpRegOp1, regOp1, 32);
                         emit->emitIns_R_R_I(INS_srli, EA_8BYTE, tmpRegOp1, tmpRegOp1, 32);
path: src/coreclr/jit/codegenriscv64.cpp
user: login: fuad1502
body: Isn't this zero extension? EDIT: oh, sorry I see what you mean, we can use `sext.w` + `sltiu` for this. Sorry for the misunderstanding! 🙏
created_at: 2025-04-23T07:38:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3208,22 +3208,23 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree)
             switch (cmpSize)
             {
                 case EA_4BYTE:
+                {
+                    regNumber tmpRegOp1 = internalRegisters.GetSingle(tree);
+                    assert(regOp1 != tmpRegOp1);
                     if (isUnsigned)
                     {
                         imm = static_cast<uint32_t>(imm);
-
-                        regNumber tmpRegOp1 = internalRegisters.GetSingle(tree);
-                        assert(regOp1 != tmpRegOp1);
-
                         emit->emitIns_R_R_I(INS_slli, EA_8BYTE, tmpRegOp1, regOp1, 32);
                         emit->emitIns_R_R_I(INS_srli, EA_8BYTE, tmpRegOp1, tmpRegOp1, 32);
path: src/coreclr/jit/codegenriscv64.cpp
user: login: tomeksowi
body: Yes, `slli+srli` zero-extends. My point was it's suboptimal, sign-extending unsigned int is better.
created_at: 2025-04-23T07:48:45Z
author_association: MEMBER

PR comments:
user: login: risc-vv
created_at: 2025-04-22T10:26:20Z
body: <details>
<summary>RISC-V Release-CLR-QEMU: 9702 / 9751 (99.50%)</summary>


```

=======================
      passed: 9702
      failed: 29
     skipped: 70
      killed: 20
------------------------
  TOTAL libs: 9821
 TOTAL tests: 9821
   REAL time: 3h 22min 1s 787ms
=======================
```
[Release-CLR-QEMU.md](https://gist.githubusercontent.com/risc-vv/312e1ad68f9b04fa0bda4ea48614e81f/raw/8597064fc2b23e599e0720a98db7474543c28d03/Release-CLR-QEMU-7dd587b05894c0d30be1a8565311a9ee1ad32016.md), [Release-CLR-QEMU.xml](https://gist.githubusercontent.com/risc-vv/5ab86a857014718b2de8a4fb6778e6f0/raw/5d75f9b12354d777cfbaac1edb41f08c028dd95a/Release-CLR-QEMU-7dd587b05894c0d30be1a8565311a9ee1ad32016.xml), [testclr_output.tar.gz](http://127.0.0.1/pipelines/Release-CLR-QEMU/166/run-tests/1/run-tests/testoutput/testclr_details.Release.tar.gz)
</details><details>
<summary>Build information and commands</summary>

GIT: `7dd587b05894c0d30be1a8565311a9ee1ad32016`
CI: `2d916d20de463f9bba05ae71b3d1f37d439a8cb1`
REPO: [dotnet/runtime](https://github.com/dotnet/runtime)
BRANCH: `main`
CONFIG: `Release`
LIB_CONFIG: `Release`

</details>
<details>
<summary>RISC-V Release-FX-QEMU: 692867 / 708108 (97.85%)</summary>


```

=======================
      passed: 692867
      failed: 1594
     skipped: 1694
      killed: 13647
------------------------
  TOTAL libs: 259
 TOTAL tests: 709802
   REAL time: 2h 28min 29s 518ms
=======================
```
[Release-FX-QEMU.md](https://gist.githubusercontent.com/risc-vv/3d3c0f37464eb39127ed1b4f877bd8e9/raw/a5696aab11280d36219119778a504763d14752d2/Release-FX-QEMU-7dd587b05894c0d30be1a8565311a9ee1ad32016.md), [Release-FX-QEMU.xml](http://127.0.0.1/pipelines/Release-FX-QEMU/159/run-tests/1/run-tests/testoutput/report.xml.gz), [testfx_output.tar.gz](http://127.0.0.1/pipelines/Release-FX-QEMU/159/run-tests/1/run-tests/testoutput/testfx_details.Release.tar.gz)
</details><details>
<summary>Build information and commands</summary>

GIT: `7dd587b05894c0d30be1a8565311a9ee1ad32016`
CI: `2d916d20de463f9bba05ae71b3d1f37d439a8cb1`
REPO: [dotnet/runtime](https://github.com/dotnet/runtime)
BRANCH: `main`
CONFIG: `Release`
LIB_CONFIG: `Release`

</details>
author_association: NONE
user: login: tomeksowi
created_at: 2025-04-22T10:44:21Z
body: [LA build failure](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1022714&view=logs&j=ca5ebf53-26df-50e3-0a4c-8171492489cd&t=2a6b699c-a3e2-5aac-5f86-63b4b87c5538) looks unrelated
author_association: MEMBER
user: login: am11
created_at: 2025-04-22T16:55:52Z
body: > [LA build failure](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1022714&view=logs&j=ca5ebf53-26df-50e3-0a4c-8171492489cd&t=2a6b699c-a3e2-5aac-5f86-63b4b87c5538) looks unrelated

Yup, it was a restore issue due to a recent change, fixed in main.
author_association: MEMBER
user: login: BruceForstall
created_at: 2025-04-25T18:16:40Z
body: /azp run runtime-coreclr outerloop
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-25T18:17:01Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: BruceForstall
created_at: 2025-04-27T17:47:46Z
body: /ba-g unrelated failures
author_association: CONTRIBUTOR