From a3357da0d7d6fa085f03ca2ee0bdf47f2120e622 Mon Sep 17 00:00:00 2001 From: Egor Chesakov Date: Fri, 22 Jan 2021 13:06:13 -0800 Subject: [PATCH 01/10] Add MultiplyHigh in src/coreclr/jit/hwintrinsiclistarm64.h --- src/coreclr/jit/hwintrinsiclistarm64.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/jit/hwintrinsiclistarm64.h b/src/coreclr/jit/hwintrinsiclistarm64.h index 6de9ca2b158b1e..da9f788db4989c 100644 --- a/src/coreclr/jit/hwintrinsiclistarm64.h +++ b/src/coreclr/jit/hwintrinsiclistarm64.h @@ -530,6 +530,7 @@ HARDWARE_INTRINSIC(ArmBase, ReverseElementBits, // Base 64-bit only Intrinsics HARDWARE_INTRINSIC(ArmBase_Arm64, LeadingSignCount, 0, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_cls, INS_invalid, INS_cls, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Scalar, HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoFloatingPointUsed) HARDWARE_INTRINSIC(ArmBase_Arm64, LeadingZeroCount, 0, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_clz, INS_clz, INS_invalid, INS_invalid}, HW_Category_Scalar, HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoFloatingPointUsed) +HARDWARE_INTRINSIC(ArmBase_Arm64, MultiplyHigh, 0, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_smulh, INS_umulh, INS_invalid, INS_invalid}, HW_Category_Scalar, HW_Flag_NoFloatingPointUsed) HARDWARE_INTRINSIC(ArmBase_Arm64, ReverseElementBits, 0, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_rbit, INS_rbit, INS_invalid, INS_invalid}, HW_Category_Scalar, HW_Flag_NoFloatingPointUsed) // *************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** From 8e84e0a986e58ef32ef3054ae41066637d54c872 Mon Sep 17 00:00:00 2001 From: Egor Chesakov Date: Fri, 22 Jan 2021 13:13:47 -0800 Subject: [PATCH 02/10] Add MultiplyHigh in ArmBase.cs ArmBase.PlatformNotSupported.cs --- .../Intrinsics/Arm/ArmBase.PlatformNotSupported.cs | 10 ++++++++++ .../src/System/Runtime/Intrinsics/Arm/ArmBase.cs | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Arm/ArmBase.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Arm/ArmBase.PlatformNotSupported.cs index 48bafcb0489f19..af23cef761ad92 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Arm/ArmBase.PlatformNotSupported.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Arm/ArmBase.PlatformNotSupported.cs @@ -47,6 +47,16 @@ internal Arm64() { } /// public static int LeadingZeroCount(ulong value) { throw new PlatformNotSupportedException(); } + /// + /// A64: SMULH Xd, Xn, Xm + /// + public static long MultiplyHigh(long left, long right) { throw new PlatformNotSupportedException(); } + + /// + /// A64: UMULH Xd, Xn, Xm + /// + public static ulong MultiplyHigh(ulong left, ulong right) { throw new PlatformNotSupportedException(); } + /// /// A64: RBIT Xd, Xn /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Arm/ArmBase.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Arm/ArmBase.cs index 7e47ef60d150b8..4900e618cf2dc3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Arm/ArmBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Arm/ArmBase.cs @@ -43,6 +43,16 @@ internal Arm64() { } /// public static int LeadingZeroCount(ulong value) => LeadingZeroCount(value); + /// + /// A64: SMULH Xd, Xn, Xm + /// + public static long MultiplyHigh(long left, long right) => MultiplyHigh(left, right); + + /// + /// A64: UMULH Xd, Xn, Xm + /// + public static ulong MultiplyHigh(ulong left, ulong right) => MultiplyHigh(left, right); + /// /// A64: RBIT Xd, Xn /// From ea5504b588f403b23a076c585d6d1f3753a297f8 Mon Sep 17 00:00:00 2001 From: Egor Chesakov Date: Fri, 22 Jan 2021 14:22:55 -0800 Subject: [PATCH 03/10] Update System.Runtime.Intrinsics.cs --- .../System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs index fbb78cb5533feb..33fc1f798e5510 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -2563,6 +2563,8 @@ internal Arm64() { } public static int LeadingSignCount(long value) { throw null; } public static int LeadingZeroCount(long value) { throw null; } public static int LeadingZeroCount(ulong value) { throw null; } + public static long MultiplyHigh(long left, long right) { throw null; } + public static ulong MultiplyHigh(ulong left, ulong right) { throw null; } public static long ReverseElementBits(long value) { throw null; } public static ulong ReverseElementBits(ulong value) { throw null; } } From b3b4dfac1922ee82ade9666de03105e52288b5e2 Mon Sep 17 00:00:00 2001 From: Egor Chesakov Date: Fri, 22 Jan 2021 14:24:42 -0800 Subject: [PATCH 04/10] Use ArmBase.Arm64.MultiplyHigh in BigMul in Math.cs --- .../System.Private.CoreLib/src/System/Math.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 47ff242192afc3..365b5491eecb08 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -151,6 +151,11 @@ public static unsafe ulong BigMul(ulong a, ulong b, out ulong low) low = tmp; return high; } + else if (ArmBase.Arm64.IsSupported) + { + low = a * b; + return ArmBase.Arm64.MultiplyHigh(a, b); + } return SoftwareFallback(a, b, out low); @@ -185,6 +190,12 @@ static ulong SoftwareFallback(ulong a, ulong b, out ulong low) /// The high 64-bit of the product of the specied numbers. public static long BigMul(long a, long b, out long low) { + if (ArmBase.Arm64.IsSupported) + { + low = a * b; + return ArmBase.Arm64.MultiplyHigh(a, b); + } + ulong high = BigMul((ulong)a, (ulong)b, out ulong ulow); low = (long)ulow; return (long)high - ((a >> 63) & b) - ((b >> 63) & a); From 1567a18840759f49f59e635e53464cf482c03332 Mon Sep 17 00:00:00 2001 From: Egor Chesakov Date: Fri, 22 Jan 2021 17:37:34 -0800 Subject: [PATCH 05/10] Implement MultiplyHigh in src/coreclr/jit/hwintrinsic.cpp --- src/coreclr/jit/hwintrinsic.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index 0aeab9b9aacf66..2f589a4db9c838 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -1062,6 +1062,18 @@ GenTree* Compiler::impHWIntrinsic(NamedIntrinsic intrinsic, retNode->AsHWIntrinsic()->SetAuxiliaryType(getBaseTypeOfSIMDType(sigReader.op2ClsHnd)); break; + case NI_ArmBase_Arm64_MultiplyHigh: + if (sig->retType == CORINFO_TYPE_ULONG) + { + retNode->AsHWIntrinsic()->gtSIMDBaseType = TYP_ULONG; + } + else + { + assert(sig->retType == CORINFO_TYPE_LONG); + retNode->AsHWIntrinsic()->gtSIMDBaseType = TYP_LONG; + } + break; + default: break; } From 59945ef645cb04b6616e01c891888b7fe61f86db Mon Sep 17 00:00:00 2001 From: Egor Chesakov Date: Fri, 22 Jan 2021 17:50:32 -0800 Subject: [PATCH 06/10] Add MultiplyHigh in GenerateTests.csx --- src/tests/JIT/HardwareIntrinsics/Arm/Shared/GenerateTests.csx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/JIT/HardwareIntrinsics/Arm/Shared/GenerateTests.csx b/src/tests/JIT/HardwareIntrinsics/Arm/Shared/GenerateTests.csx index e20d87414c251f..ec6da1aa16e206 100644 --- a/src/tests/JIT/HardwareIntrinsics/Arm/Shared/GenerateTests.csx +++ b/src/tests/JIT/HardwareIntrinsics/Arm/Shared/GenerateTests.csx @@ -2355,6 +2355,8 @@ private static readonly (string templateFileName, Dictionary tem ("ScalarUnOpTest.template", new Dictionary { ["TestName"] = "LeadingSignCount_Int64", ["Isa"] = "ArmBase.Arm64", ["Method"] = "LeadingSignCount", ["RetBaseType"] = "Int32", ["Op1BaseType"] = "Int64", ["NextValueOp1"] = "TestLibrary.Generator.GetInt64()", ["ValidateResult"] = "int expectedResult = 0; for (int index = 62; (((ulong)data >> index) & 1) == (((ulong)data >> 63) & 1); index--) { expectedResult++; } isUnexpectedResult = (expectedResult != result);" }), ("ScalarUnOpTest.template", new Dictionary { ["TestName"] = "LeadingZeroCount_Int64", ["Isa"] = "ArmBase.Arm64", ["Method"] = "LeadingZeroCount", ["RetBaseType"] = "Int32", ["Op1BaseType"] = "Int64", ["NextValueOp1"] = "TestLibrary.Generator.GetInt64()", ["ValidateResult"] = "int expectedResult = 0; for (int index = 63; (((ulong)data >> index) & 1) == 0; index--) { expectedResult++; } isUnexpectedResult = (expectedResult != result);" }), ("ScalarUnOpTest.template", new Dictionary { ["TestName"] = "LeadingZeroCount_UInt64", ["Isa"] = "ArmBase.Arm64", ["Method"] = "LeadingZeroCount", ["RetBaseType"] = "Int32", ["Op1BaseType"] = "UInt64", ["NextValueOp1"] = "TestLibrary.Generator.GetUInt64()", ["ValidateResult"] = "int expectedResult = 0; for (int index = 63; ((data >> index) & 1) == 0; index--) { expectedResult++; } isUnexpectedResult = (expectedResult != result);" }), + ("ScalarBinOpTest.template", new Dictionary { ["TestName"] = "MultiplyHigh_Int64", ["Isa"] = "ArmBase.Arm64", ["Method"] = "MultiplyHigh", ["RetBaseType"] = "Int64", ["Op1BaseType"] = "Int64", ["Op2BaseType"] = "Int64", ["NextValueOp1"] = "-TestLibrary.Generator.GetInt64()", ["NextValueOp2"] = "-TestLibrary.Generator.GetInt64()", ["ValidateResult"] = "isUnexpectedResult = Helpers.MultiplyHigh(left, right) != result;" }), + ("ScalarBinOpTest.template", new Dictionary { ["TestName"] = "MultiplyHigh_UInt64", ["Isa"] = "ArmBase.Arm64", ["Method"] = "MultiplyHigh", ["RetBaseType"] = "UInt64", ["Op1BaseType"] = "UInt64", ["Op2BaseType"] = "UInt64", ["NextValueOp1"] = "TestLibrary.Generator.GetUInt64()", ["NextValueOp2"] = "TestLibrary.Generator.GetUInt64()", ["ValidateResult"] = "isUnexpectedResult = Helpers.MultiplyHigh(left, right) != result;" }), ("ScalarUnOpTest.template", new Dictionary { ["TestName"] = "ReverseElementBits_Int64", ["Isa"] = "ArmBase.Arm64", ["Method"] = "ReverseElementBits", ["RetBaseType"] = "Int64", ["Op1BaseType"] = "Int64", ["NextValueOp1"] = "TestLibrary.Generator.GetInt64()", ["ValidateResult"] = "isUnexpectedResult = Helpers.ReverseElementBits(data) != result;" }), ("ScalarUnOpTest.template", new Dictionary { ["TestName"] = "ReverseElementBits_UInt64", ["Isa"] = "ArmBase.Arm64", ["Method"] = "ReverseElementBits", ["RetBaseType"] = "UInt64", ["Op1BaseType"] = "UInt64", ["NextValueOp1"] = "TestLibrary.Generator.GetUInt64()", ["ValidateResult"] = "isUnexpectedResult = Helpers.ReverseElementBits(data) != result;" }), }; From 3391d1205e8fd73f263bc9f3845c444069dd7886 Mon Sep 17 00:00:00 2001 From: Egor Chesakov Date: Fri, 22 Jan 2021 17:52:43 -0800 Subject: [PATCH 07/10] Update src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/* --- .../Arm/ArmBase.Arm64/ArmBase.Arm64_r.csproj | 2 + .../Arm/ArmBase.Arm64/ArmBase.Arm64_ro.csproj | 2 + .../Arm/ArmBase.Arm64/MultiplyHigh.Int64.cs | 241 ++++++++++++++++++ .../Arm/ArmBase.Arm64/MultiplyHigh.UInt64.cs | 241 ++++++++++++++++++ .../ArmBase.Arm64/Program.ArmBase.Arm64.cs | 2 + 5 files changed, 488 insertions(+) create mode 100644 src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/MultiplyHigh.Int64.cs create mode 100644 src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/MultiplyHigh.UInt64.cs diff --git a/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/ArmBase.Arm64_r.csproj b/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/ArmBase.Arm64_r.csproj index 0c6670f9e1a719..f16c8017a6ee2e 100644 --- a/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/ArmBase.Arm64_r.csproj +++ b/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/ArmBase.Arm64_r.csproj @@ -12,6 +12,8 @@ + + diff --git a/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/ArmBase.Arm64_ro.csproj b/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/ArmBase.Arm64_ro.csproj index 2b5bab9b7132eb..255b8f027ea2e7 100644 --- a/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/ArmBase.Arm64_ro.csproj +++ b/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/ArmBase.Arm64_ro.csproj @@ -12,6 +12,8 @@ + + diff --git a/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/MultiplyHigh.Int64.cs b/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/MultiplyHigh.Int64.cs new file mode 100644 index 00000000000000..e2a8b52c656b73 --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/MultiplyHigh.Int64.cs @@ -0,0 +1,241 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/****************************************************************************** + * This file is auto-generated from a template file by the GenerateTests.csx * + * script in tests\src\JIT\HardwareIntrinsics\Arm\Shared. In order to make * + * changes, please update the corresponding template and run according to the * + * directions listed in the file. * + ******************************************************************************/ + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; + +namespace JIT.HardwareIntrinsics.Arm +{ + public static partial class Program + { + private static void MultiplyHigh_Int64() + { + var test = new ScalarBinaryOpTest__MultiplyHigh_Int64(); + + if (test.IsSupported) + { + // Validates basic functionality works, using Unsafe.ReadUnaligned + test.RunBasicScenario_UnsafeRead(); + + // Validates calling via reflection works, using Unsafe.ReadUnaligned + test.RunReflectionScenario_UnsafeRead(); + + // Validates passing a static member works + test.RunClsVarScenario(); + + // Validates passing a local works, using Unsafe.ReadUnaligned + test.RunLclVarScenario_UnsafeRead(); + + // Validates passing the field of a local class works + test.RunClassLclFldScenario(); + + // Validates passing an instance member of a class works + test.RunClassFldScenario(); + + // Validates passing the field of a local struct works + test.RunStructLclFldScenario(); + + // Validates passing an instance member of a struct works + test.RunStructFldScenario(); + } + else + { + // Validates we throw on unsupported hardware + test.RunUnsupportedScenario(); + } + + if (!test.Succeeded) + { + throw new Exception("One or more scenarios did not complete as expected."); + } + } + } + + public sealed unsafe class ScalarBinaryOpTest__MultiplyHigh_Int64 + { + private struct TestStruct + { + public Int64 _fld1; + public Int64 _fld2; + + public static TestStruct Create() + { + var testStruct = new TestStruct(); + + testStruct._fld1 = -TestLibrary.Generator.GetInt64(); + testStruct._fld2 = -TestLibrary.Generator.GetInt64(); + + return testStruct; + } + + public void RunStructFldScenario(ScalarBinaryOpTest__MultiplyHigh_Int64 testClass) + { + var result = ArmBase.Arm64.MultiplyHigh(_fld1, _fld2); + testClass.ValidateResult(_fld1, _fld2, result); + } + } + + private static Int64 _data1; + private static Int64 _data2; + + private static Int64 _clsVar1; + private static Int64 _clsVar2; + + private Int64 _fld1; + private Int64 _fld2; + + static ScalarBinaryOpTest__MultiplyHigh_Int64() + { + _clsVar1 = -TestLibrary.Generator.GetInt64(); + _clsVar2 = -TestLibrary.Generator.GetInt64(); + } + + public ScalarBinaryOpTest__MultiplyHigh_Int64() + { + Succeeded = true; + + _fld1 = -TestLibrary.Generator.GetInt64(); + _fld2 = -TestLibrary.Generator.GetInt64(); + + _data1 = -TestLibrary.Generator.GetInt64(); + _data2 = -TestLibrary.Generator.GetInt64(); + } + + public bool IsSupported => ArmBase.Arm64.IsSupported; + + public bool Succeeded { get; set; } + + public void RunBasicScenario_UnsafeRead() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunBasicScenario_UnsafeRead)); + + var result = ArmBase.Arm64.MultiplyHigh( + Unsafe.ReadUnaligned(ref Unsafe.As(ref _data1)), + Unsafe.ReadUnaligned(ref Unsafe.As(ref _data2)) + ); + + ValidateResult(_data1, _data2, result); + } + + public void RunReflectionScenario_UnsafeRead() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunReflectionScenario_UnsafeRead)); + + var result = typeof(ArmBase.Arm64).GetMethod(nameof(ArmBase.Arm64.MultiplyHigh), new Type[] { typeof(Int64), typeof(Int64) }) + .Invoke(null, new object[] { + Unsafe.ReadUnaligned(ref Unsafe.As(ref _data1)), + Unsafe.ReadUnaligned(ref Unsafe.As(ref _data2)) + }); + + ValidateResult(_data1, _data2, (Int64)result); + } + + public void RunClsVarScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunClsVarScenario)); + + var result = ArmBase.Arm64.MultiplyHigh( + _clsVar1, + _clsVar2 + ); + + ValidateResult(_clsVar1, _clsVar2, result); + } + + public void RunLclVarScenario_UnsafeRead() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunLclVarScenario_UnsafeRead)); + + var data1 = Unsafe.ReadUnaligned(ref Unsafe.As(ref _data1)); + var data2 = Unsafe.ReadUnaligned(ref Unsafe.As(ref _data2)); + var result = ArmBase.Arm64.MultiplyHigh(data1, data2); + + ValidateResult(data1, data2, result); + } + + public void RunClassLclFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunClassLclFldScenario)); + + var test = new ScalarBinaryOpTest__MultiplyHigh_Int64(); + var result = ArmBase.Arm64.MultiplyHigh(test._fld1, test._fld2); + + ValidateResult(test._fld1, test._fld2, result); + } + + public void RunClassFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunClassFldScenario)); + + var result = ArmBase.Arm64.MultiplyHigh(_fld1, _fld2); + ValidateResult(_fld1, _fld2, result); + } + + public void RunStructLclFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunStructLclFldScenario)); + + var test = TestStruct.Create(); + var result = ArmBase.Arm64.MultiplyHigh(test._fld1, test._fld2); + + ValidateResult(test._fld1, test._fld2, result); + } + + public void RunStructFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunStructFldScenario)); + + var test = TestStruct.Create(); + test.RunStructFldScenario(this); + } + + public void RunUnsupportedScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunUnsupportedScenario)); + + bool succeeded = false; + + try + { + RunBasicScenario_UnsafeRead(); + } + catch (PlatformNotSupportedException) + { + succeeded = true; + } + + if (!succeeded) + { + Succeeded = false; + } + } + + private void ValidateResult(Int64 left, Int64 right, Int64 result, [CallerMemberName] string method = "") + { + var isUnexpectedResult = false; + + isUnexpectedResult = Helpers.MultiplyHigh(left, right) != result; + + if (isUnexpectedResult) + { + TestLibrary.TestFramework.LogInformation($"{nameof(ArmBase.Arm64)}.{nameof(ArmBase.Arm64.MultiplyHigh)}(Int64, Int64): MultiplyHigh failed:"); + TestLibrary.TestFramework.LogInformation($" left: {left}"); + TestLibrary.TestFramework.LogInformation($" right: {right}"); + TestLibrary.TestFramework.LogInformation($" result: {result}"); + TestLibrary.TestFramework.LogInformation(string.Empty); + + Succeeded = false; + } + } + } +} diff --git a/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/MultiplyHigh.UInt64.cs b/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/MultiplyHigh.UInt64.cs new file mode 100644 index 00000000000000..1fa16c4fdb1f41 --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/MultiplyHigh.UInt64.cs @@ -0,0 +1,241 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/****************************************************************************** + * This file is auto-generated from a template file by the GenerateTests.csx * + * script in tests\src\JIT\HardwareIntrinsics\Arm\Shared. In order to make * + * changes, please update the corresponding template and run according to the * + * directions listed in the file. * + ******************************************************************************/ + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; + +namespace JIT.HardwareIntrinsics.Arm +{ + public static partial class Program + { + private static void MultiplyHigh_UInt64() + { + var test = new ScalarBinaryOpTest__MultiplyHigh_UInt64(); + + if (test.IsSupported) + { + // Validates basic functionality works, using Unsafe.ReadUnaligned + test.RunBasicScenario_UnsafeRead(); + + // Validates calling via reflection works, using Unsafe.ReadUnaligned + test.RunReflectionScenario_UnsafeRead(); + + // Validates passing a static member works + test.RunClsVarScenario(); + + // Validates passing a local works, using Unsafe.ReadUnaligned + test.RunLclVarScenario_UnsafeRead(); + + // Validates passing the field of a local class works + test.RunClassLclFldScenario(); + + // Validates passing an instance member of a class works + test.RunClassFldScenario(); + + // Validates passing the field of a local struct works + test.RunStructLclFldScenario(); + + // Validates passing an instance member of a struct works + test.RunStructFldScenario(); + } + else + { + // Validates we throw on unsupported hardware + test.RunUnsupportedScenario(); + } + + if (!test.Succeeded) + { + throw new Exception("One or more scenarios did not complete as expected."); + } + } + } + + public sealed unsafe class ScalarBinaryOpTest__MultiplyHigh_UInt64 + { + private struct TestStruct + { + public UInt64 _fld1; + public UInt64 _fld2; + + public static TestStruct Create() + { + var testStruct = new TestStruct(); + + testStruct._fld1 = TestLibrary.Generator.GetUInt64(); + testStruct._fld2 = TestLibrary.Generator.GetUInt64(); + + return testStruct; + } + + public void RunStructFldScenario(ScalarBinaryOpTest__MultiplyHigh_UInt64 testClass) + { + var result = ArmBase.Arm64.MultiplyHigh(_fld1, _fld2); + testClass.ValidateResult(_fld1, _fld2, result); + } + } + + private static UInt64 _data1; + private static UInt64 _data2; + + private static UInt64 _clsVar1; + private static UInt64 _clsVar2; + + private UInt64 _fld1; + private UInt64 _fld2; + + static ScalarBinaryOpTest__MultiplyHigh_UInt64() + { + _clsVar1 = TestLibrary.Generator.GetUInt64(); + _clsVar2 = TestLibrary.Generator.GetUInt64(); + } + + public ScalarBinaryOpTest__MultiplyHigh_UInt64() + { + Succeeded = true; + + _fld1 = TestLibrary.Generator.GetUInt64(); + _fld2 = TestLibrary.Generator.GetUInt64(); + + _data1 = TestLibrary.Generator.GetUInt64(); + _data2 = TestLibrary.Generator.GetUInt64(); + } + + public bool IsSupported => ArmBase.Arm64.IsSupported; + + public bool Succeeded { get; set; } + + public void RunBasicScenario_UnsafeRead() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunBasicScenario_UnsafeRead)); + + var result = ArmBase.Arm64.MultiplyHigh( + Unsafe.ReadUnaligned(ref Unsafe.As(ref _data1)), + Unsafe.ReadUnaligned(ref Unsafe.As(ref _data2)) + ); + + ValidateResult(_data1, _data2, result); + } + + public void RunReflectionScenario_UnsafeRead() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunReflectionScenario_UnsafeRead)); + + var result = typeof(ArmBase.Arm64).GetMethod(nameof(ArmBase.Arm64.MultiplyHigh), new Type[] { typeof(UInt64), typeof(UInt64) }) + .Invoke(null, new object[] { + Unsafe.ReadUnaligned(ref Unsafe.As(ref _data1)), + Unsafe.ReadUnaligned(ref Unsafe.As(ref _data2)) + }); + + ValidateResult(_data1, _data2, (UInt64)result); + } + + public void RunClsVarScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunClsVarScenario)); + + var result = ArmBase.Arm64.MultiplyHigh( + _clsVar1, + _clsVar2 + ); + + ValidateResult(_clsVar1, _clsVar2, result); + } + + public void RunLclVarScenario_UnsafeRead() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunLclVarScenario_UnsafeRead)); + + var data1 = Unsafe.ReadUnaligned(ref Unsafe.As(ref _data1)); + var data2 = Unsafe.ReadUnaligned(ref Unsafe.As(ref _data2)); + var result = ArmBase.Arm64.MultiplyHigh(data1, data2); + + ValidateResult(data1, data2, result); + } + + public void RunClassLclFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunClassLclFldScenario)); + + var test = new ScalarBinaryOpTest__MultiplyHigh_UInt64(); + var result = ArmBase.Arm64.MultiplyHigh(test._fld1, test._fld2); + + ValidateResult(test._fld1, test._fld2, result); + } + + public void RunClassFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunClassFldScenario)); + + var result = ArmBase.Arm64.MultiplyHigh(_fld1, _fld2); + ValidateResult(_fld1, _fld2, result); + } + + public void RunStructLclFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunStructLclFldScenario)); + + var test = TestStruct.Create(); + var result = ArmBase.Arm64.MultiplyHigh(test._fld1, test._fld2); + + ValidateResult(test._fld1, test._fld2, result); + } + + public void RunStructFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunStructFldScenario)); + + var test = TestStruct.Create(); + test.RunStructFldScenario(this); + } + + public void RunUnsupportedScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunUnsupportedScenario)); + + bool succeeded = false; + + try + { + RunBasicScenario_UnsafeRead(); + } + catch (PlatformNotSupportedException) + { + succeeded = true; + } + + if (!succeeded) + { + Succeeded = false; + } + } + + private void ValidateResult(UInt64 left, UInt64 right, UInt64 result, [CallerMemberName] string method = "") + { + var isUnexpectedResult = false; + + isUnexpectedResult = Helpers.MultiplyHigh(left, right) != result; + + if (isUnexpectedResult) + { + TestLibrary.TestFramework.LogInformation($"{nameof(ArmBase.Arm64)}.{nameof(ArmBase.Arm64.MultiplyHigh)}(UInt64, UInt64): MultiplyHigh failed:"); + TestLibrary.TestFramework.LogInformation($" left: {left}"); + TestLibrary.TestFramework.LogInformation($" right: {right}"); + TestLibrary.TestFramework.LogInformation($" result: {result}"); + TestLibrary.TestFramework.LogInformation(string.Empty); + + Succeeded = false; + } + } + } +} diff --git a/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/Program.ArmBase.Arm64.cs b/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/Program.ArmBase.Arm64.cs index b9d8f609e98662..b9cc6083c9c948 100644 --- a/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/Program.ArmBase.Arm64.cs +++ b/src/tests/JIT/HardwareIntrinsics/Arm/ArmBase.Arm64/Program.ArmBase.Arm64.cs @@ -15,6 +15,8 @@ static Program() ["LeadingSignCount.Int64"] = LeadingSignCount_Int64, ["LeadingZeroCount.Int64"] = LeadingZeroCount_Int64, ["LeadingZeroCount.UInt64"] = LeadingZeroCount_UInt64, + ["MultiplyHigh.Int64"] = MultiplyHigh_Int64, + ["MultiplyHigh.UInt64"] = MultiplyHigh_UInt64, ["ReverseElementBits.Int64"] = ReverseElementBits_Int64, ["ReverseElementBits.UInt64"] = ReverseElementBits_UInt64, }; From 16cd1354fd746daab1fbddc9fad2bcb8022b902c Mon Sep 17 00:00:00 2001 From: Egor Chesakov Date: Mon, 25 Jan 2021 15:54:26 -0800 Subject: [PATCH 08/10] Implement MultiplyHigh in Helpers.cs Helpers.tt --- .../HardwareIntrinsics/Arm/Shared/Helpers.cs | 32 +++++++++++++++++++ .../HardwareIntrinsics/Arm/Shared/Helpers.tt | 24 ++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/tests/JIT/HardwareIntrinsics/Arm/Shared/Helpers.cs b/src/tests/JIT/HardwareIntrinsics/Arm/Shared/Helpers.cs index ba1e776bdba00c..74e3217313ae50 100644 --- a/src/tests/JIT/HardwareIntrinsics/Arm/Shared/Helpers.cs +++ b/src/tests/JIT/HardwareIntrinsics/Arm/Shared/Helpers.cs @@ -5283,6 +5283,38 @@ private static poly128_t PolynomialMult(long op1, long op2) return result; } + public static long MultiplyHigh(long op1, long op2) + { + ulong u0, v0, w0; + long u1, v1, w1, w2, t; + u0 = (ulong)op1 & 0xFFFFFFFF; + u1 = op1 >> 32; + v0 = (ulong)op2 & 0xFFFFFFFF; + v1 = op2 >> 32; + w0 = u0 * v0; + t = u1 * (long)v0 + (long)(w0 >> 32); + w1 = t & 0xFFFFFFFF; + w2 = t >> 32; + w1 = (long)u0 * v1 + w1; + return u1 * v1 + w2 + (w1 >> 32); + } + + public static ulong MultiplyHigh(ulong op1, ulong op2) + { + ulong u0, v0, w0; + ulong u1, v1, w1, w2, t; + u0 = (ulong)op1 & 0xFFFFFFFF; + u1 = op1 >> 32; + v0 = (ulong)op2 & 0xFFFFFFFF; + v1 = op2 >> 32; + w0 = u0 * v0; + t = u1 * (ulong)v0 + (ulong)(w0 >> 32); + w1 = t & 0xFFFFFFFF; + w2 = t >> 32; + w1 = (ulong)u0 * v1 + w1; + return u1 * v1 + w2 + (w1 >> 32); + } + public static byte PolynomialMultiply(byte op1, byte op2) => (byte)PolynomialMult(op1, op2); public static ushort PolynomialMultiplyWidening(byte op1, byte op2) => PolynomialMult(op1, op2); diff --git a/src/tests/JIT/HardwareIntrinsics/Arm/Shared/Helpers.tt b/src/tests/JIT/HardwareIntrinsics/Arm/Shared/Helpers.tt index 5e472b09d7c035..b133f53e1480e4 100644 --- a/src/tests/JIT/HardwareIntrinsics/Arm/Shared/Helpers.tt +++ b/src/tests/JIT/HardwareIntrinsics/Arm/Shared/Helpers.tt @@ -1390,6 +1390,30 @@ namespace JIT.HardwareIntrinsics.Arm return result; } +<# + } + + foreach (string typeName in new string[] { "long", "ulong" }) + { + // The following implementation is adaptation of an algorithm described in + // "Hacker's Delight, 2nd Edition" by Henry S. Warren, p.174. +#> + public static <#= typeName #> MultiplyHigh(<#= typeName #> op1, <#= typeName #> op2) + { + ulong u0, v0, w0; + <#= typeName #> u1, v1, w1, w2, t; + u0 = (ulong)op1 & 0xFFFFFFFF; + u1 = op1 >> 32; + v0 = (ulong)op2 & 0xFFFFFFFF; + v1 = op2 >> 32; + w0 = u0 * v0; + t = u1 * (<#= typeName #>)v0 + (<#= typeName #>)(w0 >> 32); + w1 = t & 0xFFFFFFFF; + w2 = t >> 32; + w1 = (<#= typeName #>)u0 * v1 + w1; + return u1 * v1 + w2 + (w1 >> 32); + } + <# } From d8455c2f369b1f2f596ca154e17129ffbfe7f8d8 Mon Sep 17 00:00:00 2001 From: Egor Chesakov Date: Tue, 26 Jan 2021 18:13:01 -0800 Subject: [PATCH 09/10] Support MultiplyHigh in mono --- src/mono/mono/mini/mini-llvm.c | 16 ++++++++++++++++ src/mono/mono/mini/mini-ops.h | 2 ++ src/mono/mono/mini/simd-intrinsics-netcore.c | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c index 74d7e38f86ae7a..801a8e5cfcce78 100644 --- a/src/mono/mono/mini/mini-llvm.c +++ b/src/mono/mono/mini/mini-llvm.c @@ -9086,6 +9086,22 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) values [ins->dreg] = LLVMBuildCall (builder, get_intrins (ctx, ins->opcode == OP_LSCNT32 ? INTRINS_CTLZ_I32 : INTRINS_CTLZ_I64), args, 2, ""); break; } + case OP_ARM64_SMULH: + case OP_ARM64_UMULH: { + LLVMValueRef op1, op2; + if (ins->opcode == OP_ARM64_SMULH) { + op1 = LLVMBuildSExt (builder, lhs, LLVMInt128Type(), ""); + op2 = LLVMBuildSExt (builder, rhs, LLVMInt128Type(), ""); + } else { + op1 = LLVMBuildZExt (builder, lhs, LLVMInt128Type(), ""); + op2 = LLVMBuildZExt (builder, rhs, LLVMInt128Type(), ""); + } + LLVMValueRef mul = LLVMBuildMul (builder, op1, op2, ""); + LLVMValueRef hi64 = LLVMBuildLShr (builder, mul, + LLVMConstInt(LLVMInt128Type (), 64, FALSE), ""); + values [ins->dreg] = LLVMBuildTrunc (builder, hi64, LLVMInt64Type(), ""); + break; + } #endif case OP_DUMMY_USE: diff --git a/src/mono/mono/mini/mini-ops.h b/src/mono/mono/mini/mini-ops.h index dbbb76dad0c40c..fbc559033deeda 100644 --- a/src/mono/mono/mini/mini-ops.h +++ b/src/mono/mono/mini/mini-ops.h @@ -1578,4 +1578,6 @@ MINI_OP(OP_POPCNT64, "popcnt64", LREG, LREG, NONE) #ifdef TARGET_ARM64 MINI_OP(OP_LSCNT32, "lscnt32", IREG, IREG, NONE) MINI_OP(OP_LSCNT64, "lscnt64", LREG, LREG, NONE) +MINI_OP(OP_ARM64_SMULH, "arm64_smulh", LREG, LREG, LREG) +MINI_OP(OP_ARM64_UMULH, "arm64_umulh", LREG, LREG, LREG) #endif // TARGET_ARM64 diff --git a/src/mono/mono/mini/simd-intrinsics-netcore.c b/src/mono/mono/mini/simd-intrinsics-netcore.c index f62512feb75a6b..46563faa912adf 100644 --- a/src/mono/mono/mini/simd-intrinsics-netcore.c +++ b/src/mono/mono/mini/simd-intrinsics-netcore.c @@ -807,6 +807,7 @@ emit_invalid_operation (MonoCompile *cfg, const char* message) static SimdIntrinsic armbase_methods [] = { {SN_LeadingSignCount}, {SN_LeadingZeroCount}, + {SN_MultiplyHigh}, {SN_ReverseElementBits}, {SN_get_IsSupported} }; @@ -847,6 +848,9 @@ emit_arm64_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignatur return emit_simd_ins_for_sig (cfg, klass, arg0_i32 ? OP_LZCNT32 : OP_LZCNT64, 0, arg0_type, fsig, args); case SN_LeadingSignCount: return emit_simd_ins_for_sig (cfg, klass, arg0_i32 ? OP_LSCNT32 : OP_LSCNT64, 0, arg0_type, fsig, args); + case SN_MultiplyHigh: + return emit_simd_ins_for_sig (cfg, klass, + (arg0_type == MONO_TYPE_I8 ? OP_ARM64_SMULH : OP_ARM64_UMULH), 0, arg0_type, fsig, args); case SN_ReverseElementBits: return emit_simd_ins_for_sig (cfg, klass, (is_64bit ? OP_XOP_I8_I8 : OP_XOP_I4_I4), From 9e94fbad10a09a452cc072e8ce7ccf3fd3d8a86b Mon Sep 17 00:00:00 2001 From: Egor Chesakov Date: Wed, 27 Jan 2021 11:45:26 -0800 Subject: [PATCH 10/10] Add spaces before parens in src/mono/mono/mini/mini-llvm.c --- src/mono/mono/mini/mini-llvm.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c index 801a8e5cfcce78..cc1cc58b244b35 100644 --- a/src/mono/mono/mini/mini-llvm.c +++ b/src/mono/mono/mini/mini-llvm.c @@ -9090,16 +9090,16 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) case OP_ARM64_UMULH: { LLVMValueRef op1, op2; if (ins->opcode == OP_ARM64_SMULH) { - op1 = LLVMBuildSExt (builder, lhs, LLVMInt128Type(), ""); - op2 = LLVMBuildSExt (builder, rhs, LLVMInt128Type(), ""); + op1 = LLVMBuildSExt (builder, lhs, LLVMInt128Type (), ""); + op2 = LLVMBuildSExt (builder, rhs, LLVMInt128Type (), ""); } else { - op1 = LLVMBuildZExt (builder, lhs, LLVMInt128Type(), ""); - op2 = LLVMBuildZExt (builder, rhs, LLVMInt128Type(), ""); + op1 = LLVMBuildZExt (builder, lhs, LLVMInt128Type (), ""); + op2 = LLVMBuildZExt (builder, rhs, LLVMInt128Type (), ""); } LLVMValueRef mul = LLVMBuildMul (builder, op1, op2, ""); LLVMValueRef hi64 = LLVMBuildLShr (builder, mul, - LLVMConstInt(LLVMInt128Type (), 64, FALSE), ""); - values [ins->dreg] = LLVMBuildTrunc (builder, hi64, LLVMInt64Type(), ""); + LLVMConstInt (LLVMInt128Type (), 64, FALSE), ""); + values [ins->dreg] = LLVMBuildTrunc (builder, hi64, LLVMInt64Type (), ""); break; } #endif