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;
}
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)
// ***************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
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);
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
///
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; }
}
diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c
index 74d7e38f86ae7a..cc1cc58b244b35 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),
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,
};
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;" }),
};
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);
+ }
+
<#
}