From 382b1ac81aca26a4fe18028fc5822fb8d1224b37 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 8 Dec 2025 11:10:31 -0800 Subject: [PATCH 01/11] First pass at test structure --- src/tests/JIT/Methodical/Methodical_d2.csproj | 2 + src/tests/JIT/Methodical/Methodical_do.csproj | 1 + src/tests/JIT/Methodical/Methodical_r2.csproj | 1 + src/tests/JIT/Methodical/Methodical_ro.csproj | 1 + .../int64/unsigned/implicit_promotion.cs | 57 ++++++++++++ .../int64/unsigned/implicit_promotion_il.il | 88 +++++++++++++++++++ .../unsigned/implicit_promotion_il.ilproj | 10 +++ 7 files changed, 160 insertions(+) create mode 100644 src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs create mode 100644 src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il create mode 100644 src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.ilproj diff --git a/src/tests/JIT/Methodical/Methodical_d2.csproj b/src/tests/JIT/Methodical/Methodical_d2.csproj index 3053cf52ca45d9..a5bb37cde67e9d 100644 --- a/src/tests/JIT/Methodical/Methodical_d2.csproj +++ b/src/tests/JIT/Methodical/Methodical_d2.csproj @@ -11,6 +11,7 @@ + @@ -45,6 +46,7 @@ + diff --git a/src/tests/JIT/Methodical/Methodical_do.csproj b/src/tests/JIT/Methodical/Methodical_do.csproj index 7447e30fe06523..e5ee6396e7ccac 100644 --- a/src/tests/JIT/Methodical/Methodical_do.csproj +++ b/src/tests/JIT/Methodical/Methodical_do.csproj @@ -183,6 +183,7 @@ + diff --git a/src/tests/JIT/Methodical/Methodical_r2.csproj b/src/tests/JIT/Methodical/Methodical_r2.csproj index f07b36dfa1e971..cbbd1e07efd06d 100644 --- a/src/tests/JIT/Methodical/Methodical_r2.csproj +++ b/src/tests/JIT/Methodical/Methodical_r2.csproj @@ -45,6 +45,7 @@ + diff --git a/src/tests/JIT/Methodical/Methodical_ro.csproj b/src/tests/JIT/Methodical/Methodical_ro.csproj index edabd5c971a472..eff1c1980a6ca9 100644 --- a/src/tests/JIT/Methodical/Methodical_ro.csproj +++ b/src/tests/JIT/Methodical/Methodical_ro.csproj @@ -183,6 +183,7 @@ + diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs new file mode 100644 index 00000000000000..4b7f908baa42d7 --- /dev/null +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Xunit; +using JitTest_implicit_promotion; +using implicit_promotion; + +namespace JitTest_implicit_promotion +{ + public class Test + { + [Theory] + [InlineData("add.ovf.un", (int)0x1, -2, -1, (long)0x00000000FFFFFFFF)] + [InlineData("add.ovf", (int)0x1, -2, -1, -1)] + public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) + { + nint a_nint = (nint)a; + nint result = 0; + switch(opcode) + { + case "add.ovf.un": + result = Operator.add_ovf_un_I_i32(a_nint, b); + break; + case "add.ovf": + result = Operator.add_ovf_I_i32(a_nint, b); + break; + default: + throw new ArgumentException("Invalid opcode"); + } + + Assert.Equal(Environment.Is64BitProcess ? (nint)resultI_i32_64Bit_expected : (nint)resultI_i32_32Bit_expected, result); + } + + [Theory] + [InlineData("add.ovf.un", -2, 1, -1, (long)0x00000000FFFFFFFF)] + [InlineData("add.ovf", -2, 1, -1, -1)] + public static void testi32_I_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) + { + nint b_nint = (nint)b; + nint result = 0; + switch(opcode) + { + case "add.ovf.un": + result = Operator.add_ovf_un_i32_I(a, b_nint); + break; + case "add.ovf": + result = Operator.add_ovf_i32_I(a, b_nint); + break; + default: + throw new ArgumentException("Invalid opcode"); + } + + Assert.Equal(Environment.Is64BitProcess ? (nint)resultI_i32_64Bit_expected : (nint)resultI_i32_32Bit_expected, result); + } + } +} diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il new file mode 100644 index 00000000000000..53e530fc18a8c7 --- /dev/null +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + + + + + + +.assembly extern mscorlib { } +.assembly extern System.Console +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) + .ver 4:0:0:0 +} +.assembly implicit_promotion_il +{ +} +.assembly extern xunit.core {} +// MVID: {964B45BB-9F5B-4A2B-9ECD-E062E2FE8E23} +.namespace implicit_promotion +{ + .class public auto ansi Operator + extends ['mscorlib']System.Object + { + .method public hidebysig static native int + add_ovf_un_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + add.ovf.un + ret + } // end of method 'Test::add_ovf_un_I_i32' + + .method public hidebysig static native int + add_ovf_un_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + add.ovf.un + ret + } // end of method 'Test::add_ovf_un_i32_I' + + .method public hidebysig static native int + add_ovf_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + add.ovf + ret + } // end of method 'Test::add_ovf_I_i32' + + .method public hidebysig static native int + add_ovf_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + add.ovf + ret + } // end of method 'Test::add_ovf_i32_I' + + .method public hidebysig specialname rtspecialname + instance void .ctor() il managed + { + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void ['mscorlib']System.Object::.ctor() + IL_0006: ret + } // end of method 'Test::.ctor' + + } // end of class 'Test' + +} // end of namespace 'JitTest_addsub_unsigned_il' + +//*********** DISASSEMBLY COMPLETE *********************** diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.ilproj b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.ilproj new file mode 100644 index 00000000000000..8a3f6ca933bfaf --- /dev/null +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.ilproj @@ -0,0 +1,10 @@ + + + + + PdbOnly + + + + + From d52e0504584d4bad8790f4ceb079a0e1c67bed39 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 8 Dec 2025 13:47:11 -0800 Subject: [PATCH 02/11] Add tests and fixes for the various simple integer arithmetic opcodes --- src/coreclr/interpreter/compiler.cpp | 14 +- .../int64/unsigned/implicit_promotion.cs | 90 ++++++ .../int64/unsigned/implicit_promotion_il.il | 276 +++++++++++++++++- 3 files changed, 372 insertions(+), 8 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index fe416738fa506e..3a319b7db41c86 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -2751,14 +2751,24 @@ void InterpCompiler::EmitBinaryArithmeticOp(int32_t opBase) else { #if TARGET_64BIT + // For arithmetic operations, when we do this conversion if it is an UNSIGNED operation + // convert to I8 from U4 not from I4. + + InterpOpcode convOpForI4ToIConversions = INTOP_CONV_I8_I4; + if ((opBase == INTOP_ADD_OVF_UN_I4) || + (opBase == INTOP_SUB_OVF_UN_I4) || + (opBase == INTOP_MUL_OVF_UN_I4)) + { + convOpForI4ToIConversions = INTOP_CONV_U8_U4; + } if (type1 == StackTypeI8 && type2 == StackTypeI4) { - EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_I8_I4); + EmitConv(m_pStackPointer - 1, StackTypeI8, convOpForI4ToIConversions); type2 = StackTypeI8; } else if (type1 == StackTypeI4 && type2 == StackTypeI8) { - EmitConv(m_pStackPointer - 2, StackTypeI8, INTOP_CONV_I8_I4); + EmitConv(m_pStackPointer - 2, StackTypeI8, convOpForI4ToIConversions); type1 = StackTypeI8; } #endif diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs index 4b7f908baa42d7..41bafa5bb7d7f9 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs @@ -11,20 +11,65 @@ namespace JitTest_implicit_promotion public class Test { [Theory] + [InlineData("add", (int)0x1, -2, -1, -1)] [InlineData("add.ovf.un", (int)0x1, -2, -1, (long)0x00000000FFFFFFFF)] [InlineData("add.ovf", (int)0x1, -2, -1, -1)] + [InlineData("sub", -1, -1, 0, 0)] + [InlineData("sub.ovf", -1, -1, 0, 0)] + [InlineData("sub.ovf.un", -1, -1, 0, unchecked((long)0xFFFFFFFF00000000))] + [InlineData("mul", (int)0x2, -1, -2, -2)] + [InlineData("mul.ovf", (int)0x2, -1, -2, -2)] + [InlineData("mul.ovf.un", (int)0x2, -1, -2, 0x1FFFFFFFE)] + [InlineData("div", -1, -1, 1, 1)] + [InlineData("div.un", -1, -1, 1, 1)] + [InlineData("rem", -1, -1, 0, 0)] + [InlineData("rem.un", -1, -1, 0, 0)] public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) { nint a_nint = (nint)a; nint result = 0; + switch(opcode) { + case "add": + result = Operator.add_I_i32(a_nint, b); + break; case "add.ovf.un": result = Operator.add_ovf_un_I_i32(a_nint, b); break; case "add.ovf": result = Operator.add_ovf_I_i32(a_nint, b); break; + case "sub": + result = Operator.sub_I_i32(a_nint, b); + break; + case "sub.ovf": + result = Operator.sub_ovf_I_i32(a_nint, b); + break; + case "sub.ovf.un": + result = Operator.sub_ovf_un_I_i32(a_nint, b); + break; + case "mul": + result = Operator.mul_I_i32(a_nint, b); + break; + case "mul.ovf": + result = Operator.mul_ovf_I_i32(a_nint, b); + break; + case "mul.ovf.un": + result = Operator.mul_ovf_un_I_i32(a_nint, b); + break; + case "div": + result = Operator.div_I_i32(a_nint, b); + break; + case "div.un": + result = Operator.div_un_I_i32(a_nint, b); + break; + case "rem": + result = Operator.rem_I_i32(a_nint, b); + break; + case "rem.un": + result = Operator.rem_un_I_i32(a_nint, b); + break; default: throw new ArgumentException("Invalid opcode"); } @@ -33,20 +78,65 @@ public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32 } [Theory] + [InlineData("add", -2, 1, -1, -1)] [InlineData("add.ovf.un", -2, 1, -1, (long)0x00000000FFFFFFFF)] [InlineData("add.ovf", -2, 1, -1, -1)] + [InlineData("sub", -1, -1, 0, 0)] + [InlineData("sub.ovf", -1, -1, 0, 0)] + [InlineData("sub.ovf.un", -1, 1, -2, 0xFFFFFFFE)] + [InlineData("mul", -1, 2,-2, -2)] + [InlineData("mul.ovf", -1, 2, -2, -2)] + [InlineData("mul.ovf.un", -1, 2, -2, 0x1FFFFFFFE)] + [InlineData("div", -1, -1, 1, 1)] + [InlineData("div.un", -1, -1, 1, 1)] + [InlineData("rem", -1, -1, 0, 0)] + [InlineData("rem.un", -1, -1, 0, 0)] public static void testi32_I_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) { nint b_nint = (nint)b; nint result = 0; + switch(opcode) { + case "add": + result = Operator.add_i32_I(a, b_nint); + break; case "add.ovf.un": result = Operator.add_ovf_un_i32_I(a, b_nint); break; case "add.ovf": result = Operator.add_ovf_i32_I(a, b_nint); break; + case "sub": + result = Operator.sub_i32_I(a, b_nint); + break; + case "sub.ovf": + result = Operator.sub_ovf_i32_I(a, b_nint); + break; + case "sub.ovf.un": + result = Operator.sub_ovf_un_i32_I(a, b_nint); + break; + case "mul": + result = Operator.mul_i32_I(a, b_nint); + break; + case "mul.ovf": + result = Operator.mul_ovf_i32_I(a, b_nint); + break; + case "mul.ovf.un": + result = Operator.mul_ovf_un_i32_I(a, b_nint); + break; + case "div": + result = Operator.div_i32_I(a, b_nint); + break; + case "div.un": + result = Operator.div_un_i32_I(a, b_nint); + break; + case "rem": + result = Operator.rem_i32_I(a, b_nint); + break; + case "rem.un": + result = Operator.rem_un_i32_I(a, b_nint); + break; default: throw new ArgumentException("Invalid opcode"); } diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il index 53e530fc18a8c7..8c2823840978c7 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il @@ -23,6 +23,54 @@ .class public auto ansi Operator extends ['mscorlib']System.Object { + .method public hidebysig static native int + add_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + add + ret + } // end of method 'Test::add_I_i32' + + .method public hidebysig static native int + add_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + add + ret + } // end of method 'Test::add_i32_I' + + .method public hidebysig static native int + add_ovf_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + add.ovf + ret + } // end of method 'Test::add_ovf_I_i32' + + .method public hidebysig static native int + add_ovf_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + add.ovf + ret + } // end of method 'Test::add_ovf_i32_I' + .method public hidebysig static native int add_ovf_un_I_i32(native int a, int32 b) il managed @@ -48,28 +96,244 @@ } // end of method 'Test::add_ovf_un_i32_I' .method public hidebysig static native int - add_ovf_I_i32(native int a, + sub_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + sub + ret + } // end of method 'Test::sub_I_i32' + + .method public hidebysig static native int + sub_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + sub + ret + } // end of method 'Test::sub_i32_I' + + .method public hidebysig static native int + sub_ovf_I_i32(native int a, int32 b) il managed { // Code size 161 (0xa1) .maxstack 2 ldarg.0 ldarg.1 - add.ovf + sub.ovf ret - } // end of method 'Test::add_ovf_I_i32' + } // end of method 'Test::sub_ovf_I_i32' .method public hidebysig static native int - add_ovf_i32_I(int32 a, + sub_ovf_i32_I(int32 a, native int b) il managed { // Code size 161 (0xa1) .maxstack 2 ldarg.0 ldarg.1 - add.ovf + sub.ovf ret - } // end of method 'Test::add_ovf_i32_I' + } // end of method 'Test::sub_ovf_i32_I' + + .method public hidebysig static native int + sub_ovf_un_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + sub.ovf.un + ret + } // end of method 'Test::sub_ovf_un_I_i32' + + .method public hidebysig static native int + sub_ovf_un_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + sub.ovf.un + ret + } // end of method 'Test::sub_ovf_un_i32' + + .method public hidebysig static native int + mul_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + mul + ret + } // end of method 'Test::mul_I_i32' + + .method public hidebysig static native int + mul_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + mul + ret + } // end of method 'Test::mul_i32_I' + + .method public hidebysig static native int + mul_ovf_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + mul.ovf + ret + } // end of method 'Test::mul_ovf_I_i32' + + .method public hidebysig static native int + mul_ovf_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + mul.ovf + ret + } // end of method 'Test::mul_ovf_i32_I' + + .method public hidebysig static native int + mul_ovf_un_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + mul.ovf.un + ret + } // end of method 'Test::mul_ovf_un_I_i32' + + .method public hidebysig static native int + mul_ovf_un_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + mul.ovf.un + ret + } // end of method 'Test::mul_ovf_un_i32' + + .method public hidebysig static native int + div_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + div + ret + } // end of method 'Test::div_I_i32' + + .method public hidebysig static native int + div_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + div + ret + } // end of method 'Test::div_i32_I' + + .method public hidebysig static native int + div_un_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + div.un + ret + } // end of method 'Test::div_un_I_i32' + + .method public hidebysig static native int + div_un_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + div.un + ret + } // end of method 'Test::div_un_i32' + + .method public hidebysig static native int + rem_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + rem + ret + } // end of method 'Test::rem_I_i32' + + .method public hidebysig static native int + rem_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + rem + ret + } // end of method 'Test::rem_i32_I' + + .method public hidebysig static native int + rem_un_I_i32(native int a, + int32 b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + rem.un + ret + } // end of method 'Test::rem_un_I_i32' + + .method public hidebysig static native int + rem_un_i32_I(int32 a, + native int b) il managed + { + // Code size 161 (0xa1) + .maxstack 2 + ldarg.0 + ldarg.1 + rem.un + ret + } // end of method 'Test::rem_un_i32' .method public hidebysig specialname rtspecialname instance void .ctor() il managed From 8d5517a323b57b3ccb4d60496c678b4adc032139 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 8 Dec 2025 14:05:49 -0800 Subject: [PATCH 03/11] Add testing for logical operators --- .../int64/unsigned/implicit_promotion.cs | 24 +++++++ .../int64/unsigned/implicit_promotion_il.il | 66 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs index 41bafa5bb7d7f9..6b9d81fae150f2 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs @@ -24,6 +24,9 @@ public class Test [InlineData("div.un", -1, -1, 1, 1)] [InlineData("rem", -1, -1, 0, 0)] [InlineData("rem.un", -1, -1, 0, 0)] + [InlineData("and", -1, -1, -1, -1)] + [InlineData("or", 0, -1, -1, -1)] + [InlineData("xor", -1, -1, 0, 0)] public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) { nint a_nint = (nint)a; @@ -70,6 +73,15 @@ public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32 case "rem.un": result = Operator.rem_un_I_i32(a_nint, b); break; + case "and": + result = Operator.and_I_i32(a_nint, b); + break; + case "or": + result = Operator.or_I_i32(a_nint, b); + break; + case "xor": + result = Operator.xor_I_i32(a_nint, b); + break; default: throw new ArgumentException("Invalid opcode"); } @@ -91,6 +103,9 @@ public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32 [InlineData("div.un", -1, -1, 1, 1)] [InlineData("rem", -1, -1, 0, 0)] [InlineData("rem.un", -1, -1, 0, 0)] + [InlineData("and", -1, -1, -1, -1)] + [InlineData("or", -1, 0, -1, -1)] + [InlineData("xor", -1, -1, 0, 0)] public static void testi32_I_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) { nint b_nint = (nint)b; @@ -137,6 +152,15 @@ public static void testi32_I_opcode(string opcode, int a, int b, int resultI_i32 case "rem.un": result = Operator.rem_un_i32_I(a, b_nint); break; + case "and": + result = Operator.and_i32_I(a, b_nint); + break; + case "or": + result = Operator.or_i32_I(a, b_nint); + break; + case "xor": + result = Operator.xor_i32_I(a, b_nint); + break; default: throw new ArgumentException("Invalid opcode"); } diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il index 8c2823840978c7..f4e93c0c59a7f5 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il @@ -335,6 +335,72 @@ ret } // end of method 'Test::rem_un_i32' + .method public hidebysig static native int + and_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + and + ret + } // end of method 'Test::and_I_i32' + + .method public hidebysig static native int + and_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + and + ret + } // end of method 'Test::and_i32_I' + + .method public hidebysig static native int + or_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + or + ret + } // end of method 'Test::or_I_i32' + + .method public hidebysig static native int + or_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + or + ret + } // end of method 'Test::or_i32_I' + + .method public hidebysig static native int + xor_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + xor + ret + } // end of method 'Test::xor_I_i32' + + .method public hidebysig static native int + xor_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + xor + ret + } // end of method 'Test::xor_i32_I' + .method public hidebysig specialname rtspecialname instance void .ctor() il managed { From 4981e691441b7070781378771a44a2b8f8b4213a Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 8 Dec 2025 14:39:26 -0800 Subject: [PATCH 04/11] Tests for the compare opcodes --- .../int64/unsigned/implicit_promotion.cs | 40 +++++++ .../int64/unsigned/implicit_promotion_il.il | 110 ++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs index 6b9d81fae150f2..78a0d3ca6d5181 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs @@ -27,6 +27,11 @@ public class Test [InlineData("and", -1, -1, -1, -1)] [InlineData("or", 0, -1, -1, -1)] [InlineData("xor", -1, -1, 0, 0)] + [InlineData("ceq", -1, -1, 1, 1)] + [InlineData("cgt", -2, -3, 1, 1)] + [InlineData("cgt.un", -2, -1, 0, 0)] + [InlineData("clt", -1, -2, 0, 0)] + [InlineData("clt.un", -2, -1, 1, 1)] public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) { nint a_nint = (nint)a; @@ -82,6 +87,21 @@ public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32 case "xor": result = Operator.xor_I_i32(a_nint, b); break; + case "ceq": + result = Operator.ceq_I_i32(a_nint, b); + break; + case "cgt": + result = Operator.cgt_I_i32(a_nint, b); + break; + case "cgt.un": + result = Operator.cgt_un_I_i32(a_nint, b); + break; + case "clt": + result = Operator.clt_I_i32(a_nint, b); + break; + case "clt.un": + result = Operator.clt_un_I_i32(a_nint, b); + break; default: throw new ArgumentException("Invalid opcode"); } @@ -106,6 +126,11 @@ public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32 [InlineData("and", -1, -1, -1, -1)] [InlineData("or", -1, 0, -1, -1)] [InlineData("xor", -1, -1, 0, 0)] + [InlineData("ceq", -1, -1, 1, 1)] + [InlineData("cgt", -2, -1, 0, 0)] + [InlineData("cgt.un", -1, -2, 1, 1)] + [InlineData("clt", -1, -2, 0, 0)] + [InlineData("clt.un", -2, -1, 1, 1)] public static void testi32_I_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) { nint b_nint = (nint)b; @@ -161,6 +186,21 @@ public static void testi32_I_opcode(string opcode, int a, int b, int resultI_i32 case "xor": result = Operator.xor_i32_I(a, b_nint); break; + case "ceq": + result = Operator.ceq_i32_I(a, b_nint); + break; + case "cgt": + result = Operator.cgt_i32_I(a, b_nint); + break; + case "cgt.un": + result = Operator.cgt_un_i32_I(a, b_nint); + break; + case "clt": + result = Operator.clt_i32_I(a, b_nint); + break; + case "clt.un": + result = Operator.clt_un_i32_I(a, b_nint); + break; default: throw new ArgumentException("Invalid opcode"); } diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il index f4e93c0c59a7f5..7b3231044f0bfd 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il @@ -401,6 +401,116 @@ ret } // end of method 'Test::xor_i32_I' + .method public hidebysig static native int + ceq_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + ceq + ret + } // end of method 'Test::ceq_I_i32' + + .method public hidebysig static native int + ceq_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + ceq + ret + } // end of method 'Test::ceq_i32_I' + + .method public hidebysig static native int + cgt_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + cgt + ret + } // end of method 'Test::cgt_I_i32' + + .method public hidebysig static native int + cgt_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + cgt + ret + } // end of method 'Test::cgt_i32_I' + + .method public hidebysig static native int + cgt_un_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + cgt.un + ret + } // end of method 'Test::cgt_un_I_i32' + + .method public hidebysig static native int + cgt_un_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + cgt.un + ret + } // end of method 'Test::cgt_un_i32_I' + + .method public hidebysig static native int + clt_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + clt + ret + } // end of method 'Test::clt_I_i32' + + .method public hidebysig static native int + clt_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + clt + ret + } // end of method 'Test::clt_i32_I' + + .method public hidebysig static native int + clt_un_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + clt.un + ret + } // end of method 'Test::clt_un_I_i32' + + .method public hidebysig static native int + clt_un_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + clt.un + ret + } // end of method 'Test::clt_un_i32_I' + .method public hidebysig specialname rtspecialname instance void .ctor() il managed { From 3fc6f622f1c2094371dfdaa5a80da4c291eed6d8 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 8 Dec 2025 15:49:17 -0800 Subject: [PATCH 05/11] More fixes and test cases for branch scenarios --- src/coreclr/interpreter/compiler.cpp | 36 ++- .../int64/unsigned/implicit_promotion.cs | 85 +++++ .../int64/unsigned/implicit_promotion_il.il | 300 ++++++++++++++++++ 3 files changed, 407 insertions(+), 14 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 3a319b7db41c86..abc9018fa80d42 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -2502,6 +2502,24 @@ void InterpCompiler::EmitOneArgBranch(InterpOpcode opcode, int32_t ilOffset, int } } +InterpOpcode InterpOpForWideningArgForImplicitUpcast(InterpOpcode opcode) +{ + switch (opcode) + { + case INTOP_BNE_UN_I4: + case INTOP_BLE_UN_I4: + case INTOP_BLT_UN_I4: + case INTOP_BGE_UN_I4: + case INTOP_BGT_UN_I4: + case INTOP_ADD_OVF_UN_I4: + case INTOP_SUB_OVF_UN_I4: + case INTOP_MUL_OVF_UN_I4: + return INTOP_CONV_U8_U4; + default: + return INTOP_CONV_I8_I4; + } +} + void InterpCompiler::EmitTwoArgBranch(InterpOpcode opcode, int32_t ilOffset, int insSize) { CHECK_STACK(2); @@ -2512,12 +2530,12 @@ void InterpCompiler::EmitTwoArgBranch(InterpOpcode opcode, int32_t ilOffset, int // emitting the conditional branch if (argType1 == StackTypeI4 && argType2 == StackTypeI8) { - EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_I8_I4); + EmitConv(m_pStackPointer - 1, StackTypeI8, InterpOpForWideningArgForImplicitUpcast(opcode)); argType1 = StackTypeI8; } else if (argType1 == StackTypeI8 && argType2 == StackTypeI4) { - EmitConv(m_pStackPointer - 2, StackTypeI8, INTOP_CONV_I8_I4); + EmitConv(m_pStackPointer - 2, StackTypeI8, InterpOpForWideningArgForImplicitUpcast(opcode)); } else if (argType1 == StackTypeR4 && argType2 == StackTypeR8) { @@ -2751,24 +2769,14 @@ void InterpCompiler::EmitBinaryArithmeticOp(int32_t opBase) else { #if TARGET_64BIT - // For arithmetic operations, when we do this conversion if it is an UNSIGNED operation - // convert to I8 from U4 not from I4. - - InterpOpcode convOpForI4ToIConversions = INTOP_CONV_I8_I4; - if ((opBase == INTOP_ADD_OVF_UN_I4) || - (opBase == INTOP_SUB_OVF_UN_I4) || - (opBase == INTOP_MUL_OVF_UN_I4)) - { - convOpForI4ToIConversions = INTOP_CONV_U8_U4; - } if (type1 == StackTypeI8 && type2 == StackTypeI4) { - EmitConv(m_pStackPointer - 1, StackTypeI8, convOpForI4ToIConversions); + EmitConv(m_pStackPointer - 1, StackTypeI8, InterpOpForWideningArgForImplicitUpcast((InterpOpcode)opBase)); type2 = StackTypeI8; } else if (type1 == StackTypeI4 && type2 == StackTypeI8) { - EmitConv(m_pStackPointer - 2, StackTypeI8, convOpForI4ToIConversions); + EmitConv(m_pStackPointer - 2, StackTypeI8, InterpOpForWideningArgForImplicitUpcast((InterpOpcode)opBase)); type1 = StackTypeI8; } #endif diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs index 78a0d3ca6d5181..a52e2dd3c0d3d4 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs @@ -32,11 +32,26 @@ public class Test [InlineData("cgt.un", -2, -1, 0, 0)] [InlineData("clt", -1, -2, 0, 0)] [InlineData("clt.un", -2, -1, 1, 1)] + [InlineData("beq", -1, -1, 1, 1)] + [InlineData("bne.un", -1, -1, 0, 1)] + [InlineData("blt", -1, -1, 0, 0)] + [InlineData("blt.un", -2, -1, 1, 0)] + [InlineData("ble", -1, -1, 1, 1)] + [InlineData("ble.un", -1, -1, 1, 0)] + [InlineData("bgt", -1, -1, 0, 0)] + [InlineData("bgt.un", -1, -1, 0, 1)] + [InlineData("bge", -1, -1, 1, 1)] + [InlineData("bge.un", -1, -2, 1, 1)] // Special case this one. public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) { nint a_nint = (nint)a; nint result = 0; + if (opcode == "bge.un") + { + a_nint = unchecked((nint)(nuint)0xFFFFFFFF); + } + switch(opcode) { case "add": @@ -102,6 +117,36 @@ public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32 case "clt.un": result = Operator.clt_un_I_i32(a_nint, b); break; + case "beq": + result = Operator.beq_I_i32(a_nint, b); + break; + case "bne.un": + result = Operator.bne_un_I_i32(a_nint, b); + break; + case "blt": + result = Operator.blt_I_i32(a_nint, b); + break; + case "blt.un": + result = Operator.blt_un_I_i32(a_nint, b); + break; + case "ble": + result = Operator.ble_I_i32(a_nint, b); + break; + case "ble.un": + result = Operator.ble_un_I_i32(a_nint, b); + break; + case "bgt": + result = Operator.bgt_I_i32(a_nint, b); + break; + case "bgt.un": + result = Operator.bgt_un_I_i32(a_nint, b); + break; + case "bge": + result = Operator.bge_I_i32(a_nint, b); + break; + case "bge.un": + result = Operator.bge_un_I_i32(a_nint, b); + break; default: throw new ArgumentException("Invalid opcode"); } @@ -131,6 +176,16 @@ public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32 [InlineData("cgt.un", -1, -2, 1, 1)] [InlineData("clt", -1, -2, 0, 0)] [InlineData("clt.un", -2, -1, 1, 1)] + [InlineData("beq", -1, -1, 1, 1)] + [InlineData("bne.un", -1, -1, 0, 1)] + [InlineData("blt", -2, -1, 1, 1)] + [InlineData("blt.un", -1, -2, 0, 1)] + [InlineData("ble", -1, -1, 1, 1)] + [InlineData("ble.un", -1, -2, 0, 1)] + [InlineData("bgt", -1, -2, 1, 1)] + [InlineData("bgt.un", -1, -2, 1, 0)] + [InlineData("bge", -1, -2, 1, 1)] + [InlineData("bge.un", -1, -2, 1, 0)] public static void testi32_I_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) { nint b_nint = (nint)b; @@ -201,6 +256,36 @@ public static void testi32_I_opcode(string opcode, int a, int b, int resultI_i32 case "clt.un": result = Operator.clt_un_i32_I(a, b_nint); break; + case "beq": + result = Operator.beq_i32_I(a, b_nint); + break; + case "bne.un": + result = Operator.bne_un_i32_I(a, b_nint); + break; + case "blt": + result = Operator.blt_i32_I(a, b_nint); + break; + case "blt.un": + result = Operator.blt_un_i32_I(a, b_nint); + break; + case "ble": + result = Operator.ble_i32_I(a, b_nint); + break; + case "ble.un": + result = Operator.ble_un_i32_I(a, b_nint); + break; + case "bgt": + result = Operator.bgt_i32_I(a, b_nint); + break; + case "bgt.un": + result = Operator.bgt_un_i32_I(a, b_nint); + break; + case "bge": + result = Operator.bge_i32_I(a, b_nint); + break; + case "bge.un": + result = Operator.bge_un_i32_I(a, b_nint); + break; default: throw new ArgumentException("Invalid opcode"); } diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il index 7b3231044f0bfd..75997676bd0282 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il @@ -511,6 +511,306 @@ ret } // end of method 'Test::clt_un_i32_I' + .method public hidebysig static native int + beq_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + beq TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::beq_I_i32' + + .method public hidebysig static native int + beq_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + beq TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::beq_i32_I' + + .method public hidebysig static native int + bne_un_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + bne.un TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::bne_un_I_i32' + + .method public hidebysig static native int + bne_un_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + bne.un TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::bne_un_i32_I' + + .method public hidebysig static native int + blt_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + blt TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::blt_I_i32' + + .method public hidebysig static native int + blt_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + blt TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::blt_i32_I' + + .method public hidebysig static native int + blt_un_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + blt.un TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::blt_un_I_i32' + + .method public hidebysig static native int + blt_un_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + blt.un TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::blt_un_i32_I' + + .method public hidebysig static native int + ble_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + ble TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::ble_I_i32' + + .method public hidebysig static native int + ble_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + ble TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::ble_i32_I' + + .method public hidebysig static native int + ble_un_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + ble.un TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::ble_un_I_i32' + + .method public hidebysig static native int + ble_un_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + ble.un TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::ble_un_i32_I' + + .method public hidebysig static native int + bgt_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + bgt TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::bgt_I_i32' + + .method public hidebysig static native int + bgt_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + bgt TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::bgt_i32_I' + + .method public hidebysig static native int + bgt_un_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + bgt.un TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::bgt_un_I_i32' + + .method public hidebysig static native int + bgt_un_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + bgt.un TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::bgt_un_i32_I' + + .method public hidebysig static native int + bge_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + bge TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::bge_I_i32' + + .method public hidebysig static native int + bge_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + bge TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::bge_i32_I' + + .method public hidebysig static native int + bge_un_I_i32(native int a, + int32 b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + bge.un TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::bge_un_I_i32' + + .method public hidebysig static native int + bge_un_i32_I(int32 a, + native int b) il managed + { + .maxstack 2 + ldarg.0 + ldarg.1 + bge.un TAKEN + ldc.i4.0 + ret + TAKEN: + ldc.i4.1 + ret + } // end of method 'Test::bge_un_i32_I' + .method public hidebysig specialname rtspecialname instance void .ctor() il managed { From 73c2c40fb1ddf0f437607573f2e65b77bff7972c Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 8 Dec 2025 15:55:46 -0800 Subject: [PATCH 06/11] Test works on box 64 and 32bit platforms --- .../Methodical/int64/unsigned/implicit_promotion.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs index a52e2dd3c0d3d4..53ed3500ceb2f4 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs @@ -51,6 +51,11 @@ public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32 { a_nint = unchecked((nint)(nuint)0xFFFFFFFF); } + if (opcode == "mul.ovf.un" && !Environment.Is64BitProcess) + { + Assert.Throws(() => Operator.mul_ovf_un_I_i32(a_nint, b)); + return; + } switch(opcode) { @@ -191,6 +196,12 @@ public static void testi32_I_opcode(string opcode, int a, int b, int resultI_i32 nint b_nint = (nint)b; nint result = 0; + if (opcode == "mul.ovf.un" && !Environment.Is64BitProcess) + { + Assert.Throws(() => Operator.mul_ovf_un_i32_I(a, b_nint)); + return; + } + switch(opcode) { case "add": From 67e9138bca813d3ef9ce983782d7d79f0be9ce96 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 8 Dec 2025 16:16:21 -0800 Subject: [PATCH 07/11] Make a single Fact instead of a giant stack of theories. This should allow for more useful optimization tests --- .../int64/unsigned/implicit_promotion.cs | 495 ++++++++---------- 1 file changed, 211 insertions(+), 284 deletions(-) diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs index 53ed3500ceb2f4..0df36c0bdd5cd7 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs @@ -10,298 +10,225 @@ namespace JitTest_implicit_promotion { public class Test { - [Theory] - [InlineData("add", (int)0x1, -2, -1, -1)] - [InlineData("add.ovf.un", (int)0x1, -2, -1, (long)0x00000000FFFFFFFF)] - [InlineData("add.ovf", (int)0x1, -2, -1, -1)] - [InlineData("sub", -1, -1, 0, 0)] - [InlineData("sub.ovf", -1, -1, 0, 0)] - [InlineData("sub.ovf.un", -1, -1, 0, unchecked((long)0xFFFFFFFF00000000))] - [InlineData("mul", (int)0x2, -1, -2, -2)] - [InlineData("mul.ovf", (int)0x2, -1, -2, -2)] - [InlineData("mul.ovf.un", (int)0x2, -1, -2, 0x1FFFFFFFE)] - [InlineData("div", -1, -1, 1, 1)] - [InlineData("div.un", -1, -1, 1, 1)] - [InlineData("rem", -1, -1, 0, 0)] - [InlineData("rem.un", -1, -1, 0, 0)] - [InlineData("and", -1, -1, -1, -1)] - [InlineData("or", 0, -1, -1, -1)] - [InlineData("xor", -1, -1, 0, 0)] - [InlineData("ceq", -1, -1, 1, 1)] - [InlineData("cgt", -2, -3, 1, 1)] - [InlineData("cgt.un", -2, -1, 0, 0)] - [InlineData("clt", -1, -2, 0, 0)] - [InlineData("clt.un", -2, -1, 1, 1)] - [InlineData("beq", -1, -1, 1, 1)] - [InlineData("bne.un", -1, -1, 0, 1)] - [InlineData("blt", -1, -1, 0, 0)] - [InlineData("blt.un", -2, -1, 1, 0)] - [InlineData("ble", -1, -1, 1, 1)] - [InlineData("ble.un", -1, -1, 1, 0)] - [InlineData("bgt", -1, -1, 0, 0)] - [InlineData("bgt.un", -1, -1, 0, 1)] - [InlineData("bge", -1, -1, 1, 1)] - [InlineData("bge.un", -1, -2, 1, 1)] // Special case this one. - public static void testI_i32_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) + // These tests attempt to verify that the implicit upcasting from I4 to I which happens on 64 bit platforms + // is done in a consistent manner across all implementations. + // Notable details of interest: + // add.ovf.un, sub.ovf.un, mul.ovf.un upcast without sign-extension. + // div.un, and rem.un upcast with sign-extension. + // clt.un, cgt.un upcast without sign-extension + // bne.un, blt.un, ble.un, bgt.un, bge.un upcast without sign-extension + public static void TestUpcastBehavior() { - nint a_nint = (nint)a; - nint result = 0; - - if (opcode == "bge.un") - { - a_nint = unchecked((nint)(nuint)0xFFFFFFFF); - } - if (opcode == "mul.ovf.un" && !Environment.Is64BitProcess) + unchecked { - Assert.Throws(() => Operator.mul_ovf_un_I_i32(a_nint, b)); - return; - } + ////////////////////////////////////////////////////////////////// + /// Test scenarios where the first operand is I and the second is i32 + ///////////////////////////////////////////////////////////////// - switch(opcode) - { - case "add": - result = Operator.add_I_i32(a_nint, b); - break; - case "add.ovf.un": - result = Operator.add_ovf_un_I_i32(a_nint, b); - break; - case "add.ovf": - result = Operator.add_ovf_I_i32(a_nint, b); - break; - case "sub": - result = Operator.sub_I_i32(a_nint, b); - break; - case "sub.ovf": - result = Operator.sub_ovf_I_i32(a_nint, b); - break; - case "sub.ovf.un": - result = Operator.sub_ovf_un_I_i32(a_nint, b); - break; - case "mul": - result = Operator.mul_I_i32(a_nint, b); - break; - case "mul.ovf": - result = Operator.mul_ovf_I_i32(a_nint, b); - break; - case "mul.ovf.un": - result = Operator.mul_ovf_un_I_i32(a_nint, b); - break; - case "div": - result = Operator.div_I_i32(a_nint, b); - break; - case "div.un": - result = Operator.div_un_I_i32(a_nint, b); - break; - case "rem": - result = Operator.rem_I_i32(a_nint, b); - break; - case "rem.un": - result = Operator.rem_un_I_i32(a_nint, b); - break; - case "and": - result = Operator.and_I_i32(a_nint, b); - break; - case "or": - result = Operator.or_I_i32(a_nint, b); - break; - case "xor": - result = Operator.xor_I_i32(a_nint, b); - break; - case "ceq": - result = Operator.ceq_I_i32(a_nint, b); - break; - case "cgt": - result = Operator.cgt_I_i32(a_nint, b); - break; - case "cgt.un": - result = Operator.cgt_un_I_i32(a_nint, b); - break; - case "clt": - result = Operator.clt_I_i32(a_nint, b); - break; - case "clt.un": - result = Operator.clt_un_I_i32(a_nint, b); - break; - case "beq": - result = Operator.beq_I_i32(a_nint, b); - break; - case "bne.un": - result = Operator.bne_un_I_i32(a_nint, b); - break; - case "blt": - result = Operator.blt_I_i32(a_nint, b); - break; - case "blt.un": - result = Operator.blt_un_I_i32(a_nint, b); - break; - case "ble": - result = Operator.ble_I_i32(a_nint, b); - break; - case "ble.un": - result = Operator.ble_un_I_i32(a_nint, b); - break; - case "bgt": - result = Operator.bgt_I_i32(a_nint, b); - break; - case "bgt.un": - result = Operator.bgt_un_I_i32(a_nint, b); - break; - case "bge": - result = Operator.bge_I_i32(a_nint, b); - break; - case "bge.un": - result = Operator.bge_un_I_i32(a_nint, b); - break; - default: - throw new ArgumentException("Invalid opcode"); - } + // add: (int)0x1 + -2 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-1) : (nint)(-1), Operator.add_I_i32((nint)0x1, -2)); - Assert.Equal(Environment.Is64BitProcess ? (nint)resultI_i32_64Bit_expected : (nint)resultI_i32_32Bit_expected, result); - } + // add.ovf.un: (int)0x1 + -2 + Assert.Equal(Environment.Is64BitProcess ? (nint)(ulong)0x00000000FFFFFFFF : (nint)(-1), Operator.add_ovf_un_I_i32((nint)0x1, -2)); - [Theory] - [InlineData("add", -2, 1, -1, -1)] - [InlineData("add.ovf.un", -2, 1, -1, (long)0x00000000FFFFFFFF)] - [InlineData("add.ovf", -2, 1, -1, -1)] - [InlineData("sub", -1, -1, 0, 0)] - [InlineData("sub.ovf", -1, -1, 0, 0)] - [InlineData("sub.ovf.un", -1, 1, -2, 0xFFFFFFFE)] - [InlineData("mul", -1, 2,-2, -2)] - [InlineData("mul.ovf", -1, 2, -2, -2)] - [InlineData("mul.ovf.un", -1, 2, -2, 0x1FFFFFFFE)] - [InlineData("div", -1, -1, 1, 1)] - [InlineData("div.un", -1, -1, 1, 1)] - [InlineData("rem", -1, -1, 0, 0)] - [InlineData("rem.un", -1, -1, 0, 0)] - [InlineData("and", -1, -1, -1, -1)] - [InlineData("or", -1, 0, -1, -1)] - [InlineData("xor", -1, -1, 0, 0)] - [InlineData("ceq", -1, -1, 1, 1)] - [InlineData("cgt", -2, -1, 0, 0)] - [InlineData("cgt.un", -1, -2, 1, 1)] - [InlineData("clt", -1, -2, 0, 0)] - [InlineData("clt.un", -2, -1, 1, 1)] - [InlineData("beq", -1, -1, 1, 1)] - [InlineData("bne.un", -1, -1, 0, 1)] - [InlineData("blt", -2, -1, 1, 1)] - [InlineData("blt.un", -1, -2, 0, 1)] - [InlineData("ble", -1, -1, 1, 1)] - [InlineData("ble.un", -1, -2, 0, 1)] - [InlineData("bgt", -1, -2, 1, 1)] - [InlineData("bgt.un", -1, -2, 1, 0)] - [InlineData("bge", -1, -2, 1, 1)] - [InlineData("bge.un", -1, -2, 1, 0)] - public static void testi32_I_opcode(string opcode, int a, int b, int resultI_i32_32Bit_expected, long resultI_i32_64Bit_expected) - { - nint b_nint = (nint)b; - nint result = 0; + // add.ovf: (int)0x1 + -2 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-1) : (nint)(-1), Operator.add_ovf_I_i32((nint)0x1, -2)); - if (opcode == "mul.ovf.un" && !Environment.Is64BitProcess) - { - Assert.Throws(() => Operator.mul_ovf_un_i32_I(a, b_nint)); - return; - } + // sub: -1 - -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.sub_I_i32((nint)(-1), -1)); - switch(opcode) - { - case "add": - result = Operator.add_i32_I(a, b_nint); - break; - case "add.ovf.un": - result = Operator.add_ovf_un_i32_I(a, b_nint); - break; - case "add.ovf": - result = Operator.add_ovf_i32_I(a, b_nint); - break; - case "sub": - result = Operator.sub_i32_I(a, b_nint); - break; - case "sub.ovf": - result = Operator.sub_ovf_i32_I(a, b_nint); - break; - case "sub.ovf.un": - result = Operator.sub_ovf_un_i32_I(a, b_nint); - break; - case "mul": - result = Operator.mul_i32_I(a, b_nint); - break; - case "mul.ovf": - result = Operator.mul_ovf_i32_I(a, b_nint); - break; - case "mul.ovf.un": - result = Operator.mul_ovf_un_i32_I(a, b_nint); - break; - case "div": - result = Operator.div_i32_I(a, b_nint); - break; - case "div.un": - result = Operator.div_un_i32_I(a, b_nint); - break; - case "rem": - result = Operator.rem_i32_I(a, b_nint); - break; - case "rem.un": - result = Operator.rem_un_i32_I(a, b_nint); - break; - case "and": - result = Operator.and_i32_I(a, b_nint); - break; - case "or": - result = Operator.or_i32_I(a, b_nint); - break; - case "xor": - result = Operator.xor_i32_I(a, b_nint); - break; - case "ceq": - result = Operator.ceq_i32_I(a, b_nint); - break; - case "cgt": - result = Operator.cgt_i32_I(a, b_nint); - break; - case "cgt.un": - result = Operator.cgt_un_i32_I(a, b_nint); - break; - case "clt": - result = Operator.clt_i32_I(a, b_nint); - break; - case "clt.un": - result = Operator.clt_un_i32_I(a, b_nint); - break; - case "beq": - result = Operator.beq_i32_I(a, b_nint); - break; - case "bne.un": - result = Operator.bne_un_i32_I(a, b_nint); - break; - case "blt": - result = Operator.blt_i32_I(a, b_nint); - break; - case "blt.un": - result = Operator.blt_un_i32_I(a, b_nint); - break; - case "ble": - result = Operator.ble_i32_I(a, b_nint); - break; - case "ble.un": - result = Operator.ble_un_i32_I(a, b_nint); - break; - case "bgt": - result = Operator.bgt_i32_I(a, b_nint); - break; - case "bgt.un": - result = Operator.bgt_un_i32_I(a, b_nint); - break; - case "bge": - result = Operator.bge_i32_I(a, b_nint); - break; - case "bge.un": - result = Operator.bge_un_i32_I(a, b_nint); - break; - default: - throw new ArgumentException("Invalid opcode"); - } + // sub.ovf: -1 - -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.sub_ovf_I_i32((nint)(-1), -1)); + + // sub.ovf.un: -1 - -1 + Assert.Equal(Environment.Is64BitProcess ? unchecked((nint)(ulong)0xFFFFFFFF00000000) : (nint)0, Operator.sub_ovf_un_I_i32((nint)(-1), -1)); + + // mul: (int)0x2 * -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-2) : (nint)(-2), Operator.mul_I_i32((nint)0x2, -1)); + + // mul.ovf: (int)0x2 * -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-2) : (nint)(-2), Operator.mul_ovf_I_i32((nint)0x2, -1)); + + // mul.ovf.un: (int)0x2 * -1 + if (!Environment.Is64BitProcess) + { + Assert.Throws(() => Operator.mul_ovf_un_I_i32((nint)0x2, -1)); + } + else + { + Assert.Equal((nint)(ulong)0x1FFFFFFFE, Operator.mul_ovf_un_I_i32((nint)0x2, -1)); + } + + // div: -1 / -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.div_I_i32((nint)(-1), -1)); + + // div.un: -1 / -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.div_un_I_i32((nint)(-1), -1)); + + // rem: -1 % -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.rem_I_i32((nint)(-1), -1)); + + // rem.un: -1 % -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.rem_un_I_i32((nint)(-1), -1)); + + // and: -1 & -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-1) : (nint)(-1), Operator.and_I_i32((nint)(-1), -1)); + + // or: 0 | -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-1) : (nint)(-1), Operator.or_I_i32((nint)0, -1)); + + // xor: -1 ^ -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.xor_I_i32((nint)(-1), -1)); + + // ceq: -1 == -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.ceq_I_i32((nint)(-1), -1)); + + // cgt: -2 > -3 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.cgt_I_i32((nint)(-2), -3)); + + // cgt.un: -2 > -1 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.cgt_un_I_i32((nint)(-2), -1)); + + // clt: -1 < -2 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.clt_I_i32((nint)(-1), -2)); + + // clt.un: -2 < -1 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.clt_un_I_i32((nint)(-2), -1)); + + // beq: -1 == -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.beq_I_i32((nint)(-1), -1)); + + // bne.un: -1 != -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)0, Operator.bne_un_I_i32((nint)(-1), -1)); + + // blt: -1 < -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.blt_I_i32((nint)(-1), -1)); + + // blt.un: -2 < -1 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)1, Operator.blt_un_I_i32((nint)(-2), -1)); + + // ble: -1 <= -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.ble_I_i32((nint)(-1), -1)); + + // ble.un: -1 <= -1 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)1, Operator.ble_un_I_i32((nint)(-1), -1)); + + // bgt: -1 > -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.bgt_I_i32((nint)(-1), -1)); - Assert.Equal(Environment.Is64BitProcess ? (nint)resultI_i32_64Bit_expected : (nint)resultI_i32_32Bit_expected, result); + // bgt.un: -1 > -1 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)0, Operator.bgt_un_I_i32((nint)(-1), -1)); + + // bge: -1 >= -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.bge_I_i32((nint)(-1), -1)); + + // bge.un: 0xFFFFFFFF >= -2 (unsigned, special case) + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.bge_un_I_i32(unchecked((nint)(nuint)0xFFFFFFFF), -2)); + + ////////////////////////////////////////////////////////////////// + /// Test scenarios where the first operand is i32 and the second is I + ///////////////////////////////////////////////////////////////// + + // add: -2 + 1 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-1) : (nint)(-1), Operator.add_i32_I(-2, (nint)1)); + + // add.ovf.un: -2 + 1 + Assert.Equal(Environment.Is64BitProcess ? (nint)(ulong)0x00000000FFFFFFFF : (nint)(-1), Operator.add_ovf_un_i32_I(-2, (nint)1)); + + // add.ovf: -2 + 1 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-1) : (nint)(-1), Operator.add_ovf_i32_I(-2, (nint)1)); + + // sub: -1 - -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.sub_i32_I(-1, (nint)(-1))); + + // sub.ovf: -1 - -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.sub_ovf_i32_I(-1, (nint)(-1))); + + // sub.ovf.un: -1 - 1 + Assert.Equal(Environment.Is64BitProcess ? (nint)(ulong)0xFFFFFFFE : (nint)(-2), Operator.sub_ovf_un_i32_I(-1, (nint)1)); + + // mul: -1 * 2 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-2) : (nint)(-2), Operator.mul_i32_I(-1, (nint)2)); + + // mul.ovf: -1 * 2 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-2) : (nint)(-2), Operator.mul_ovf_i32_I(-1, (nint)2)); + + // mul.ovf.un: -1 * 2 + if (!Environment.Is64BitProcess) + { + Assert.Throws(() => Operator.mul_ovf_un_i32_I(-1, (nint)2)); + } + else + { + Assert.Equal((nint)(ulong)0x1FFFFFFFE, Operator.mul_ovf_un_i32_I(-1, (nint)2)); + } + + // div: -1 / -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.div_i32_I(-1, (nint)(-1))); + + // div.un: -1 / -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.div_un_i32_I(-1, (nint)(-1))); + + // rem: -1 % -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.rem_i32_I(-1, (nint)(-1))); + + // rem.un: -1 % -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.rem_un_i32_I(-1, (nint)(-1))); + + // and: -1 & -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-1) : (nint)(-1), Operator.and_i32_I(-1, (nint)(-1))); + + // or: -1 | 0 + Assert.Equal(Environment.Is64BitProcess ? (nint)(-1) : (nint)(-1), Operator.or_i32_I(-1, (nint)0)); + + // xor: -1 ^ -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.xor_i32_I(-1, (nint)(-1))); + + // ceq: -1 == -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.ceq_i32_I(-1, (nint)(-1))); + + // cgt: -2 > -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.cgt_i32_I(-2, (nint)(-1))); + + // cgt.un: -1 > -2 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.cgt_un_i32_I(-1, (nint)(-2))); + + // clt: -1 < -2 + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)0, Operator.clt_i32_I(-1, (nint)(-2))); + + // clt.un: -2 < -1 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.clt_un_i32_I(-2, (nint)(-1))); + + // beq: -1 == -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.beq_i32_I(-1, (nint)(-1))); + + // bne.un: -1 != -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)0, Operator.bne_un_i32_I(-1, (nint)(-1))); + + // blt: -2 < -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.blt_i32_I(-2, (nint)(-1))); + + // blt.un: -1 < -2 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)0, Operator.blt_un_i32_I(-1, (nint)(-2))); + + // ble: -1 <= -1 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.ble_i32_I(-1, (nint)(-1))); + + // ble.un: -1 <= -2 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)0, Operator.ble_un_i32_I(-1, (nint)(-2))); + + // bgt: -1 > -2 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.bgt_i32_I(-1, (nint)(-2))); + + // bgt.un: -1 > -2 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)1, Operator.bgt_un_i32_I(-1, (nint)(-2))); + + // bge: -1 >= -2 + Assert.Equal(Environment.Is64BitProcess ? (nint)1 : (nint)1, Operator.bge_i32_I(-1, (nint)(-2))); + + // bge.un: -1 >= -2 (unsigned) + Assert.Equal(Environment.Is64BitProcess ? (nint)0 : (nint)1, Operator.bge_un_i32_I(-1, (nint)(-2))); + } } } } From 226ddb82ba790c74af218c6cc91902bfae5b76e4 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 9 Dec 2025 12:59:58 -0800 Subject: [PATCH 08/11] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/interpreter/compiler.cpp | 2 ++ .../JIT/Methodical/int64/unsigned/implicit_promotion_il.il | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index abc9018fa80d42..d4f5f3d723c8c3 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -2502,6 +2502,8 @@ void InterpCompiler::EmitOneArgBranch(InterpOpcode opcode, int32_t ilOffset, int } } +// Determines whether I4 to I8 promotion should use zero-extension (for unsigned operations) +// or sign-extension (for signed operations), based on the opcode. InterpOpcode InterpOpForWideningArgForImplicitUpcast(InterpOpcode opcode) { switch (opcode) diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il index 75997676bd0282..ab49df28961de0 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il @@ -823,6 +823,6 @@ } // end of class 'Test' -} // end of namespace 'JitTest_addsub_unsigned_il' +} // end of namespace 'implicit_promotion' //*********** DISASSEMBLY COMPLETE *********************** From 5ea57ad1d26844075438b3da759a2e248874a7e1 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 9 Dec 2025 13:41:40 -0800 Subject: [PATCH 09/11] Fix issues --- src/tests/JIT/Methodical/Methodical_do.csproj | 1 + src/tests/JIT/Methodical/Methodical_r2.csproj | 1 + src/tests/JIT/Methodical/Methodical_ro.csproj | 1 + .../int64/unsigned/implicit_promotion_il.il | 126 +++++++++--------- 4 files changed, 66 insertions(+), 63 deletions(-) diff --git a/src/tests/JIT/Methodical/Methodical_do.csproj b/src/tests/JIT/Methodical/Methodical_do.csproj index e5ee6396e7ccac..79b2779f80b8ed 100644 --- a/src/tests/JIT/Methodical/Methodical_do.csproj +++ b/src/tests/JIT/Methodical/Methodical_do.csproj @@ -9,6 +9,7 @@ + diff --git a/src/tests/JIT/Methodical/Methodical_r2.csproj b/src/tests/JIT/Methodical/Methodical_r2.csproj index cbbd1e07efd06d..b3b4e66d8cc7c0 100644 --- a/src/tests/JIT/Methodical/Methodical_r2.csproj +++ b/src/tests/JIT/Methodical/Methodical_r2.csproj @@ -11,6 +11,7 @@ + diff --git a/src/tests/JIT/Methodical/Methodical_ro.csproj b/src/tests/JIT/Methodical/Methodical_ro.csproj index eff1c1980a6ca9..2e88949d8f061f 100644 --- a/src/tests/JIT/Methodical/Methodical_ro.csproj +++ b/src/tests/JIT/Methodical/Methodical_ro.csproj @@ -9,6 +9,7 @@ + diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il index ab49df28961de0..00f98530c15434 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.il @@ -33,7 +33,7 @@ ldarg.1 add ret - } // end of method 'Test::add_I_i32' + } // end of method 'Operator::add_I_i32' .method public hidebysig static native int add_i32_I(int32 a, @@ -45,7 +45,7 @@ ldarg.1 add ret - } // end of method 'Test::add_i32_I' + } // end of method 'Operator::add_i32_I' .method public hidebysig static native int add_ovf_I_i32(native int a, @@ -57,7 +57,7 @@ ldarg.1 add.ovf ret - } // end of method 'Test::add_ovf_I_i32' + } // end of method 'Operator::add_ovf_I_i32' .method public hidebysig static native int add_ovf_i32_I(int32 a, @@ -69,7 +69,7 @@ ldarg.1 add.ovf ret - } // end of method 'Test::add_ovf_i32_I' + } // end of method 'Operator::add_ovf_i32_I' .method public hidebysig static native int add_ovf_un_I_i32(native int a, @@ -81,7 +81,7 @@ ldarg.1 add.ovf.un ret - } // end of method 'Test::add_ovf_un_I_i32' + } // end of method 'Operator::add_ovf_un_I_i32' .method public hidebysig static native int add_ovf_un_i32_I(int32 a, @@ -93,7 +93,7 @@ ldarg.1 add.ovf.un ret - } // end of method 'Test::add_ovf_un_i32_I' + } // end of method 'Operator::add_ovf_un_i32_I' .method public hidebysig static native int sub_I_i32(native int a, @@ -105,7 +105,7 @@ ldarg.1 sub ret - } // end of method 'Test::sub_I_i32' + } // end of method 'Operator::sub_I_i32' .method public hidebysig static native int sub_i32_I(int32 a, @@ -117,7 +117,7 @@ ldarg.1 sub ret - } // end of method 'Test::sub_i32_I' + } // end of method 'Operator::sub_i32_I' .method public hidebysig static native int sub_ovf_I_i32(native int a, @@ -129,7 +129,7 @@ ldarg.1 sub.ovf ret - } // end of method 'Test::sub_ovf_I_i32' + } // end of method 'Operator::sub_ovf_I_i32' .method public hidebysig static native int sub_ovf_i32_I(int32 a, @@ -141,7 +141,7 @@ ldarg.1 sub.ovf ret - } // end of method 'Test::sub_ovf_i32_I' + } // end of method 'Operator::sub_ovf_i32_I' .method public hidebysig static native int sub_ovf_un_I_i32(native int a, @@ -153,7 +153,7 @@ ldarg.1 sub.ovf.un ret - } // end of method 'Test::sub_ovf_un_I_i32' + } // end of method 'Operator::sub_ovf_un_I_i32' .method public hidebysig static native int sub_ovf_un_i32_I(int32 a, @@ -165,7 +165,7 @@ ldarg.1 sub.ovf.un ret - } // end of method 'Test::sub_ovf_un_i32' + } // end of method 'Operator::sub_ovf_un_i32' .method public hidebysig static native int mul_I_i32(native int a, @@ -177,7 +177,7 @@ ldarg.1 mul ret - } // end of method 'Test::mul_I_i32' + } // end of method 'Operator::mul_I_i32' .method public hidebysig static native int mul_i32_I(int32 a, @@ -189,7 +189,7 @@ ldarg.1 mul ret - } // end of method 'Test::mul_i32_I' + } // end of method 'Operator::mul_i32_I' .method public hidebysig static native int mul_ovf_I_i32(native int a, @@ -201,7 +201,7 @@ ldarg.1 mul.ovf ret - } // end of method 'Test::mul_ovf_I_i32' + } // end of method 'Operator::mul_ovf_I_i32' .method public hidebysig static native int mul_ovf_i32_I(int32 a, @@ -213,7 +213,7 @@ ldarg.1 mul.ovf ret - } // end of method 'Test::mul_ovf_i32_I' + } // end of method 'Operator::mul_ovf_i32_I' .method public hidebysig static native int mul_ovf_un_I_i32(native int a, @@ -225,7 +225,7 @@ ldarg.1 mul.ovf.un ret - } // end of method 'Test::mul_ovf_un_I_i32' + } // end of method 'Operator::mul_ovf_un_I_i32' .method public hidebysig static native int mul_ovf_un_i32_I(int32 a, @@ -237,7 +237,7 @@ ldarg.1 mul.ovf.un ret - } // end of method 'Test::mul_ovf_un_i32' + } // end of method 'Operator::mul_ovf_un_i32' .method public hidebysig static native int div_I_i32(native int a, @@ -249,7 +249,7 @@ ldarg.1 div ret - } // end of method 'Test::div_I_i32' + } // end of method 'Operator::div_I_i32' .method public hidebysig static native int div_i32_I(int32 a, @@ -261,7 +261,7 @@ ldarg.1 div ret - } // end of method 'Test::div_i32_I' + } // end of method 'Operator::div_i32_I' .method public hidebysig static native int div_un_I_i32(native int a, @@ -273,7 +273,7 @@ ldarg.1 div.un ret - } // end of method 'Test::div_un_I_i32' + } // end of method 'Operator::div_un_I_i32' .method public hidebysig static native int div_un_i32_I(int32 a, @@ -285,7 +285,7 @@ ldarg.1 div.un ret - } // end of method 'Test::div_un_i32' + } // end of method 'Operator::div_un_i32' .method public hidebysig static native int rem_I_i32(native int a, @@ -297,7 +297,7 @@ ldarg.1 rem ret - } // end of method 'Test::rem_I_i32' + } // end of method 'Operator::rem_I_i32' .method public hidebysig static native int rem_i32_I(int32 a, @@ -309,7 +309,7 @@ ldarg.1 rem ret - } // end of method 'Test::rem_i32_I' + } // end of method 'Operator::rem_i32_I' .method public hidebysig static native int rem_un_I_i32(native int a, @@ -321,7 +321,7 @@ ldarg.1 rem.un ret - } // end of method 'Test::rem_un_I_i32' + } // end of method 'Operator::rem_un_I_i32' .method public hidebysig static native int rem_un_i32_I(int32 a, @@ -333,7 +333,7 @@ ldarg.1 rem.un ret - } // end of method 'Test::rem_un_i32' + } // end of method 'Operator::rem_un_i32' .method public hidebysig static native int and_I_i32(native int a, @@ -344,7 +344,7 @@ ldarg.1 and ret - } // end of method 'Test::and_I_i32' + } // end of method 'Operator::and_I_i32' .method public hidebysig static native int and_i32_I(int32 a, @@ -355,7 +355,7 @@ ldarg.1 and ret - } // end of method 'Test::and_i32_I' + } // end of method 'Operator::and_i32_I' .method public hidebysig static native int or_I_i32(native int a, @@ -366,7 +366,7 @@ ldarg.1 or ret - } // end of method 'Test::or_I_i32' + } // end of method 'Operator::or_I_i32' .method public hidebysig static native int or_i32_I(int32 a, @@ -377,7 +377,7 @@ ldarg.1 or ret - } // end of method 'Test::or_i32_I' + } // end of method 'Operator::or_i32_I' .method public hidebysig static native int xor_I_i32(native int a, @@ -388,7 +388,7 @@ ldarg.1 xor ret - } // end of method 'Test::xor_I_i32' + } // end of method 'Operator::xor_I_i32' .method public hidebysig static native int xor_i32_I(int32 a, @@ -399,7 +399,7 @@ ldarg.1 xor ret - } // end of method 'Test::xor_i32_I' + } // end of method 'Operator::xor_i32_I' .method public hidebysig static native int ceq_I_i32(native int a, @@ -410,7 +410,7 @@ ldarg.1 ceq ret - } // end of method 'Test::ceq_I_i32' + } // end of method 'Operator::ceq_I_i32' .method public hidebysig static native int ceq_i32_I(int32 a, @@ -421,7 +421,7 @@ ldarg.1 ceq ret - } // end of method 'Test::ceq_i32_I' + } // end of method 'Operator::ceq_i32_I' .method public hidebysig static native int cgt_I_i32(native int a, @@ -432,7 +432,7 @@ ldarg.1 cgt ret - } // end of method 'Test::cgt_I_i32' + } // end of method 'Operator::cgt_I_i32' .method public hidebysig static native int cgt_i32_I(int32 a, @@ -443,7 +443,7 @@ ldarg.1 cgt ret - } // end of method 'Test::cgt_i32_I' + } // end of method 'Operator::cgt_i32_I' .method public hidebysig static native int cgt_un_I_i32(native int a, @@ -454,7 +454,7 @@ ldarg.1 cgt.un ret - } // end of method 'Test::cgt_un_I_i32' + } // end of method 'Operator::cgt_un_I_i32' .method public hidebysig static native int cgt_un_i32_I(int32 a, @@ -465,7 +465,7 @@ ldarg.1 cgt.un ret - } // end of method 'Test::cgt_un_i32_I' + } // end of method 'Operator::cgt_un_i32_I' .method public hidebysig static native int clt_I_i32(native int a, @@ -476,7 +476,7 @@ ldarg.1 clt ret - } // end of method 'Test::clt_I_i32' + } // end of method 'Operator::clt_I_i32' .method public hidebysig static native int clt_i32_I(int32 a, @@ -487,7 +487,7 @@ ldarg.1 clt ret - } // end of method 'Test::clt_i32_I' + } // end of method 'Operator::clt_i32_I' .method public hidebysig static native int clt_un_I_i32(native int a, @@ -498,7 +498,7 @@ ldarg.1 clt.un ret - } // end of method 'Test::clt_un_I_i32' + } // end of method 'Operator::clt_un_I_i32' .method public hidebysig static native int clt_un_i32_I(int32 a, @@ -509,7 +509,7 @@ ldarg.1 clt.un ret - } // end of method 'Test::clt_un_i32_I' + } // end of method 'Operator::clt_un_i32_I' .method public hidebysig static native int beq_I_i32(native int a, @@ -524,7 +524,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::beq_I_i32' + } // end of method 'Operator::beq_I_i32' .method public hidebysig static native int beq_i32_I(int32 a, @@ -539,7 +539,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::beq_i32_I' + } // end of method 'Operator::beq_i32_I' .method public hidebysig static native int bne_un_I_i32(native int a, @@ -554,7 +554,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::bne_un_I_i32' + } // end of method 'Operator::bne_un_I_i32' .method public hidebysig static native int bne_un_i32_I(int32 a, @@ -569,7 +569,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::bne_un_i32_I' + } // end of method 'Operator::bne_un_i32_I' .method public hidebysig static native int blt_I_i32(native int a, @@ -584,7 +584,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::blt_I_i32' + } // end of method 'Operator::blt_I_i32' .method public hidebysig static native int blt_i32_I(int32 a, @@ -599,7 +599,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::blt_i32_I' + } // end of method 'Operator::blt_i32_I' .method public hidebysig static native int blt_un_I_i32(native int a, @@ -614,7 +614,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::blt_un_I_i32' + } // end of method 'Operator::blt_un_I_i32' .method public hidebysig static native int blt_un_i32_I(int32 a, @@ -629,7 +629,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::blt_un_i32_I' + } // end of method 'Operator::blt_un_i32_I' .method public hidebysig static native int ble_I_i32(native int a, @@ -644,7 +644,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::ble_I_i32' + } // end of method 'Operator::ble_I_i32' .method public hidebysig static native int ble_i32_I(int32 a, @@ -659,7 +659,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::ble_i32_I' + } // end of method 'Operator::ble_i32_I' .method public hidebysig static native int ble_un_I_i32(native int a, @@ -674,7 +674,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::ble_un_I_i32' + } // end of method 'Operator::ble_un_I_i32' .method public hidebysig static native int ble_un_i32_I(int32 a, @@ -689,7 +689,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::ble_un_i32_I' + } // end of method 'Operator::ble_un_i32_I' .method public hidebysig static native int bgt_I_i32(native int a, @@ -704,7 +704,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::bgt_I_i32' + } // end of method 'Operator::bgt_I_i32' .method public hidebysig static native int bgt_i32_I(int32 a, @@ -719,7 +719,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::bgt_i32_I' + } // end of method 'Operator::bgt_i32_I' .method public hidebysig static native int bgt_un_I_i32(native int a, @@ -734,7 +734,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::bgt_un_I_i32' + } // end of method 'Operator::bgt_un_I_i32' .method public hidebysig static native int bgt_un_i32_I(int32 a, @@ -749,7 +749,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::bgt_un_i32_I' + } // end of method 'Operator::bgt_un_i32_I' .method public hidebysig static native int bge_I_i32(native int a, @@ -764,7 +764,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::bge_I_i32' + } // end of method 'Operator::bge_I_i32' .method public hidebysig static native int bge_i32_I(int32 a, @@ -779,7 +779,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::bge_i32_I' + } // end of method 'Operator::bge_i32_I' .method public hidebysig static native int bge_un_I_i32(native int a, @@ -794,7 +794,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::bge_un_I_i32' + } // end of method 'Operator::bge_un_I_i32' .method public hidebysig static native int bge_un_i32_I(int32 a, @@ -809,7 +809,7 @@ TAKEN: ldc.i4.1 ret - } // end of method 'Test::bge_un_i32_I' + } // end of method 'Operator::bge_un_i32_I' .method public hidebysig specialname rtspecialname instance void .ctor() il managed @@ -819,7 +819,7 @@ IL_0000: ldarg.0 IL_0001: call instance void ['mscorlib']System.Object::.ctor() IL_0006: ret - } // end of method 'Test::.ctor' + } // end of method 'Operator::.ctor' } // end of class 'Test' From 55db6983d452f864a012ea51f71c8af79b32808c Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 9 Dec 2025 16:14:47 -0800 Subject: [PATCH 10/11] Actually get the test to run in CI by adding attribute --- src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs index 0df36c0bdd5cd7..0a5d2d281076ab 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs @@ -17,6 +17,7 @@ public class Test // div.un, and rem.un upcast with sign-extension. // clt.un, cgt.un upcast without sign-extension // bne.un, blt.un, ble.un, bgt.un, bge.un upcast without sign-extension + [Fact] public static void TestUpcastBehavior() { unchecked From d3b5ec82939ee963e0c282fa8ab0255859c0bb18 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 10 Dec 2025 10:12:35 -0800 Subject: [PATCH 11/11] Disable test on mono --- src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs index 0a5d2d281076ab..b4701fabbb2b40 100644 --- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs +++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion.cs @@ -18,6 +18,7 @@ public class Test // clt.un, cgt.un upcast without sign-extension // bne.un, blt.un, ble.un, bgt.un, bge.un upcast without sign-extension [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/122398", TestRuntimes.Mono)] public static void TestUpcastBehavior() { unchecked