| 1 | // Licensed to the .NET Foundation under one or more agreements. | 
| 2 | // The .NET Foundation licenses this file to you under the MIT license. | 
| 3 | // See the LICENSE file in the project root for more information. | 
| 4 |  | 
| 5 | #include "jitpch.h" | 
| 6 | #include "hwintrinsic.h" | 
| 7 |  | 
| 8 | #ifdef FEATURE_HW_INTRINSICS | 
| 9 |  | 
| 10 | namespace IsaFlag | 
| 11 | { | 
| 12 | enum Flag | 
| 13 | { | 
| 14 | #define HARDWARE_INTRINSIC_CLASS(flag, isa) isa = 1ULL << InstructionSet_##isa, | 
| 15 | #include "hwintrinsiclistArm64.h" | 
| 16 |     None     = 0, | 
| 17 |     Base     = 1ULL << InstructionSet_Base, | 
| 18 |     EveryISA = ~0ULL | 
| 19 | }; | 
| 20 |  | 
| 21 | Flag operator|(Flag a, Flag b) | 
| 22 | { | 
| 23 |     return Flag(uint64_t(a) | uint64_t(b)); | 
| 24 | } | 
| 25 |  | 
| 26 | Flag flag(InstructionSet isa) | 
| 27 | { | 
| 28 |     return Flag(1ULL << isa); | 
| 29 | } | 
| 30 | } | 
| 31 |  | 
| 32 | // clang-format off | 
| 33 | static const HWIntrinsicInfo hwIntrinsicInfoArray[] = { | 
| 34 |     // Add lookupHWIntrinsic special cases see lookupHWIntrinsic() below | 
| 35 |     //     NI_ARM64_IsSupported_True is used to expand get_IsSupported to const true | 
| 36 |     //     NI_ARM64_IsSupported_False is used to expand get_IsSupported to const false | 
| 37 |     //     NI_ARM64_PlatformNotSupported to throw PlatformNotSupported exception for every intrinsic not supported on the running platform | 
| 38 |     {NI_ARM64_IsSupported_True,     "get_IsSupported" ,                 IsaFlag::EveryISA, HWIntrinsicInfo::IsSupported, HWIntrinsicInfo::None, {}}, | 
| 39 |     {NI_ARM64_IsSupported_False,    "::NI_ARM64_IsSupported_False" ,    IsaFlag::EveryISA, HWIntrinsicInfo::IsSupported, HWIntrinsicInfo::None, {}}, | 
| 40 |     {NI_ARM64_PlatformNotSupported, "::NI_ARM64_PlatformNotSupported" , IsaFlag::EveryISA, HWIntrinsicInfo::Unsupported, HWIntrinsicInfo::None, {}}, | 
| 41 | #define HARDWARE_INTRINSIC(id, isa, name, form, i0, i1, i2, flags) \ | 
| 42 |     {id,                            #name,                             IsaFlag::isa,      HWIntrinsicInfo::form,        HWIntrinsicInfo::flags, { i0, i1, i2 }}, | 
| 43 | #include "hwintrinsiclistArm64.h" | 
| 44 | }; | 
| 45 | // clang-format on | 
| 46 |  | 
| 47 | //------------------------------------------------------------------------ | 
| 48 | // lookup: Gets the HWIntrinsicInfo associated with a given NamedIntrinsic | 
| 49 | // | 
| 50 | // Arguments: | 
| 51 | //    id -- The NamedIntrinsic associated with the HWIntrinsic to lookup | 
| 52 | // | 
| 53 | // Return Value: | 
| 54 | //    The HWIntrinsicInfo associated with id | 
| 55 | const HWIntrinsicInfo& HWIntrinsicInfo::lookup(NamedIntrinsic id) | 
| 56 | { | 
| 57 |     assert(id != NI_Illegal); | 
| 58 |  | 
| 59 |     assert(id > NI_HW_INTRINSIC_START); | 
| 60 |     assert(id < NI_HW_INTRINSIC_END); | 
| 61 |  | 
| 62 |     return hwIntrinsicInfoArray[id - NI_HW_INTRINSIC_START - 1]; | 
| 63 | } | 
| 64 |  | 
| 65 | //------------------------------------------------------------------------ | 
| 66 | // lookupHWIntrinsicISA: map class name to InstructionSet value | 
| 67 | // | 
| 68 | // Arguments: | 
| 69 | //    className -- class name in System.Runtime.Intrinsics.Arm.Arm64 | 
| 70 | // | 
| 71 | // Return Value: | 
| 72 | //    Id for the ISA class if enabled. | 
| 73 | // | 
| 74 | InstructionSet Compiler::lookupHWIntrinsicISA(const char* className) | 
| 75 | { | 
| 76 |     if (className != nullptr) | 
| 77 |     { | 
| 78 |         if (strcmp(className, "Base" ) == 0) | 
| 79 |             return InstructionSet_Base; | 
| 80 | #define HARDWARE_INTRINSIC_CLASS(flag, isa)                                                                            \ | 
| 81 |     if (strcmp(className, #isa) == 0)                                                                                  \ | 
| 82 |         return InstructionSet_##isa; | 
| 83 | #include "hwintrinsiclistArm64.h" | 
| 84 |     } | 
| 85 |  | 
| 86 |     return InstructionSet_NONE; | 
| 87 | } | 
| 88 |  | 
| 89 | //------------------------------------------------------------------------ | 
| 90 | // lookupHWIntrinsic: map intrinsic name to named intrinsic value | 
| 91 | // | 
| 92 | // Arguments: | 
| 93 | //    methodName -- name of the intrinsic function. | 
| 94 | //    isa        -- instruction set of the intrinsic. | 
| 95 | // | 
| 96 | // Return Value: | 
| 97 | //    Id for the hardware intrinsic. | 
| 98 | // | 
| 99 | // TODO-Throughput: replace sequential search by hash lookup | 
| 100 | NamedIntrinsic Compiler::lookupHWIntrinsic(const char* className, const char* methodName) | 
| 101 | { | 
| 102 |     InstructionSet isa    = lookupHWIntrinsicISA(className); | 
| 103 |     NamedIntrinsic result = NI_Illegal; | 
| 104 |     if (isa != InstructionSet_NONE) | 
| 105 |     { | 
| 106 |         IsaFlag::Flag isaFlag = IsaFlag::flag(isa); | 
| 107 |         for (int i = 0; i < NI_HW_INTRINSIC_END - NI_HW_INTRINSIC_START; i++) | 
| 108 |         { | 
| 109 |             if ((isaFlag & hwIntrinsicInfoArray[i].isaflags) && strcmp(methodName, hwIntrinsicInfoArray[i].name) == 0) | 
| 110 |             { | 
| 111 |                 if (compSupportsHWIntrinsic(isa)) | 
| 112 |                 { | 
| 113 |                     // Intrinsic is supported on platform | 
| 114 |                     result = hwIntrinsicInfoArray[i].id; | 
| 115 |                 } | 
| 116 |                 else | 
| 117 |                 { | 
| 118 |                     // When the intrinsic class is not supported | 
| 119 |                     // Return NI_ARM64_PlatformNotSupported for all intrinsics | 
| 120 |                     // Return NI_ARM64_IsSupported_False for the IsSupported property | 
| 121 |                     result = (hwIntrinsicInfoArray[i].id != NI_ARM64_IsSupported_True) ? NI_ARM64_PlatformNotSupported | 
| 122 |                                                                                        : NI_ARM64_IsSupported_False; | 
| 123 |                 } | 
| 124 |                 break; | 
| 125 |             } | 
| 126 |         } | 
| 127 |     } | 
| 128 |     return result; | 
| 129 | } | 
| 130 |  | 
| 131 | //------------------------------------------------------------------------ | 
| 132 | // impCheckImmediate: check if immediate is const and in range for inlining | 
| 133 | // | 
| 134 | bool Compiler::impCheckImmediate(GenTree* immediateOp, unsigned int max) | 
| 135 | { | 
| 136 |     return immediateOp->IsCnsIntOrI() && (immediateOp->AsIntConCommon()->IconValue() < max); | 
| 137 | } | 
| 138 |  | 
| 139 | //------------------------------------------------------------------------ | 
| 140 | // isFullyImplementedIsa: Gets a value that indicates whether the InstructionSet is fully implemented | 
| 141 | // | 
| 142 | // Arguments: | 
| 143 | //    isa - The InstructionSet to check | 
| 144 | // | 
| 145 | // Return Value: | 
| 146 | //    true if isa is supported; otherwise, false | 
| 147 | // | 
| 148 | // Notes: | 
| 149 | //    This currently returns true for all partially-implemented ISAs. | 
| 150 | //    TODO-Bug: Set this to return the correct values as GH 20427 is resolved. | 
| 151 | // | 
| 152 | bool HWIntrinsicInfo::isFullyImplementedIsa(InstructionSet isa) | 
| 153 | { | 
| 154 |     switch (isa) | 
| 155 |     { | 
| 156 |         case InstructionSet_Base: | 
| 157 |         case InstructionSet_Crc32: | 
| 158 |         case InstructionSet_Aes: | 
| 159 |         case InstructionSet_Simd: | 
| 160 |         case InstructionSet_Sha1: | 
| 161 |         case InstructionSet_Sha256: | 
| 162 |             return true; | 
| 163 |  | 
| 164 |         default: | 
| 165 |             assert(!"Unexpected Arm64 HW intrinsics ISA" ); | 
| 166 |             return false; | 
| 167 |     } | 
| 168 | } | 
| 169 |  | 
| 170 | //------------------------------------------------------------------------ | 
| 171 | // isScalarIsa: Gets a value that indicates whether the InstructionSet is scalar | 
| 172 | // | 
| 173 | // Arguments: | 
| 174 | //    isa - The InstructionSet to check | 
| 175 | // | 
| 176 | // Return Value: | 
| 177 | //    true if isa is scalar; otherwise, false | 
| 178 | bool HWIntrinsicInfo::isScalarIsa(InstructionSet isa) | 
| 179 | { | 
| 180 |     switch (isa) | 
| 181 |     { | 
| 182 |         case InstructionSet_Base: | 
| 183 |         case InstructionSet_Crc32: | 
| 184 |             return true; | 
| 185 |  | 
| 186 |         case InstructionSet_Aes: | 
| 187 |         case InstructionSet_Simd: | 
| 188 |         case InstructionSet_Sha1: | 
| 189 |         case InstructionSet_Sha256: | 
| 190 |             return false; | 
| 191 |  | 
| 192 |         default: | 
| 193 |             assert(!"Unexpected Arm64 HW intrinsics ISA" ); | 
| 194 |             return true; | 
| 195 |     } | 
| 196 | } | 
| 197 |  | 
| 198 | //------------------------------------------------------------------------ | 
| 199 | // compSupportsHWIntrinsic: compiler support of hardware intrinsics | 
| 200 | // | 
| 201 | // Arguments: | 
| 202 | //    isa - Instruction set | 
| 203 | // Return Value: | 
| 204 | //    true if | 
| 205 | //    - isa is a scalar ISA | 
| 206 | //    - isa is a SIMD ISA and featureSIMD=true | 
| 207 | //    - isa is fully implemented or EnableIncompleteISAClass=true | 
| 208 | bool Compiler::compSupportsHWIntrinsic(InstructionSet isa) | 
| 209 | { | 
| 210 |     return (featureSIMD || HWIntrinsicInfo::isScalarIsa(isa)) && ( | 
| 211 | #ifdef DEBUG | 
| 212 |                                                                      JitConfig.EnableIncompleteISAClass() || | 
| 213 | #endif | 
| 214 |                                                                      HWIntrinsicInfo::isFullyImplementedIsa(isa)); | 
| 215 | } | 
| 216 |  | 
| 217 | //------------------------------------------------------------------------ | 
| 218 | // lookupNumArgs: gets the number of arguments for the hardware intrinsic. | 
| 219 | // This attempts to do a table based lookup but will fallback to the number | 
| 220 | // of operands in 'node' if the table entry is -1. | 
| 221 | // | 
| 222 | // Arguments: | 
| 223 | //    node      -- GenTreeHWIntrinsic* node with nullptr default value | 
| 224 | // | 
| 225 | // Return Value: | 
| 226 | //     number of arguments | 
| 227 | // | 
| 228 | int HWIntrinsicInfo::lookupNumArgs(const GenTreeHWIntrinsic* node) | 
| 229 | { | 
| 230 |     NamedIntrinsic intrinsic = node->gtHWIntrinsicId; | 
| 231 |  | 
| 232 |     assert(intrinsic != NI_Illegal); | 
| 233 |     assert(intrinsic > NI_HW_INTRINSIC_START && intrinsic < NI_HW_INTRINSIC_END); | 
| 234 |  | 
| 235 |     GenTree* op1     = node->gtGetOp1(); | 
| 236 |     GenTree* op2     = node->gtGetOp2(); | 
| 237 |     int      numArgs = 0; | 
| 238 |  | 
| 239 |     if (op1 == nullptr) | 
| 240 |     { | 
| 241 |         return 0; | 
| 242 |     } | 
| 243 |  | 
| 244 |     if (op1->OperIsList()) | 
| 245 |     { | 
| 246 |         numArgs              = 0; | 
| 247 |         GenTreeArgList* list = op1->AsArgList(); | 
| 248 |  | 
| 249 |         while (list != nullptr) | 
| 250 |         { | 
| 251 |             numArgs++; | 
| 252 |             list = list->Rest(); | 
| 253 |         } | 
| 254 |  | 
| 255 |         // We should only use a list if we have 3 operands. | 
| 256 |         assert(numArgs >= 3); | 
| 257 |         return numArgs; | 
| 258 |     } | 
| 259 |  | 
| 260 |     if (op2 == nullptr) | 
| 261 |     { | 
| 262 |         return 1; | 
| 263 |     } | 
| 264 |  | 
| 265 |     return 2; | 
| 266 | } | 
| 267 |  | 
| 268 | //------------------------------------------------------------------------ | 
| 269 | // impHWIntrinsic: dispatch hardware intrinsics to their own implementation | 
| 270 | // function | 
| 271 | // | 
| 272 | // Arguments: | 
| 273 | //    intrinsic -- id of the intrinsic function. | 
| 274 | //    method    -- method handle of the intrinsic function. | 
| 275 | //    sig       -- signature of the intrinsic call | 
| 276 | // | 
| 277 | // Return Value: | 
| 278 | //    the expanded intrinsic. | 
| 279 | // | 
| 280 | GenTree* Compiler::impHWIntrinsic(NamedIntrinsic        intrinsic, | 
| 281 |                                   CORINFO_METHOD_HANDLE method, | 
| 282 |                                   CORINFO_SIG_INFO*     sig, | 
| 283 |                                   bool                  mustExpand) | 
| 284 | { | 
| 285 |     GenTree*             retNode       = nullptr; | 
| 286 |     GenTree*             op1           = nullptr; | 
| 287 |     GenTree*             op2           = nullptr; | 
| 288 |     GenTree*             op3           = nullptr; | 
| 289 |     CORINFO_CLASS_HANDLE simdClass     = nullptr; | 
| 290 |     var_types            simdType      = TYP_UNKNOWN; | 
| 291 |     var_types            simdBaseType  = TYP_UNKNOWN; | 
| 292 |     unsigned             simdSizeBytes = 0; | 
| 293 |  | 
| 294 |     switch (HWIntrinsicInfo::lookup(intrinsic).form) | 
| 295 |     { | 
| 296 |         case HWIntrinsicInfo::SimdBinaryOp: | 
| 297 |         case HWIntrinsicInfo::SimdInsertOp: | 
| 298 |         case HWIntrinsicInfo::SimdSelectOp: | 
| 299 |         case HWIntrinsicInfo::SimdSetAllOp: | 
| 300 |         case HWIntrinsicInfo::SimdUnaryOp: | 
| 301 |         case HWIntrinsicInfo::SimdBinaryRMWOp: | 
| 302 |         case HWIntrinsicInfo::SimdTernaryRMWOp: | 
| 303 |         case HWIntrinsicInfo::Sha1HashOp: | 
| 304 |             simdClass = sig->retTypeClass; | 
| 305 |             break; | 
| 306 |         case HWIntrinsicInfo::SimdExtractOp: | 
| 307 |             info.compCompHnd->getArgType(sig, sig->args, &simdClass); | 
| 308 |             break; | 
| 309 |         default: | 
| 310 |             break; | 
| 311 |     } | 
| 312 |  | 
| 313 |     // Simd instantiation type check | 
| 314 |     if (simdClass != nullptr) | 
| 315 |     { | 
| 316 |         if (featureSIMD) | 
| 317 |         { | 
| 318 |             compFloatingPointUsed = true; | 
| 319 |  | 
| 320 |             simdBaseType = getBaseTypeAndSizeOfSIMDType(simdClass, &simdSizeBytes); | 
| 321 |         } | 
| 322 |  | 
| 323 |         if (simdBaseType == TYP_UNKNOWN) | 
| 324 |         { | 
| 325 |             return impUnsupportedHWIntrinsic(CORINFO_HELP_THROW_PLATFORM_NOT_SUPPORTED, method, sig, mustExpand); | 
| 326 |         } | 
| 327 |         simdType = getSIMDTypeForSize(simdSizeBytes); | 
| 328 |     } | 
| 329 |  | 
| 330 |     switch (HWIntrinsicInfo::lookup(intrinsic).form) | 
| 331 |     { | 
| 332 |         case HWIntrinsicInfo::IsSupported: | 
| 333 |             return gtNewIconNode((intrinsic == NI_ARM64_IsSupported_True) ? 1 : 0); | 
| 334 |  | 
| 335 |         case HWIntrinsicInfo::Unsupported: | 
| 336 |             return impUnsupportedHWIntrinsic(CORINFO_HELP_THROW_PLATFORM_NOT_SUPPORTED, method, sig, mustExpand); | 
| 337 |  | 
| 338 |         case HWIntrinsicInfo::UnaryOp: | 
| 339 |             op1 = impPopStack().val; | 
| 340 |  | 
| 341 |             return gtNewScalarHWIntrinsicNode(JITtype2varType(sig->retType), op1, intrinsic); | 
| 342 |  | 
| 343 |         case HWIntrinsicInfo::SimdBinaryOp: | 
| 344 |         case HWIntrinsicInfo::SimdBinaryRMWOp: | 
| 345 |             // op1 is the first operand | 
| 346 |             // op2 is the second operand | 
| 347 |             op2 = impSIMDPopStack(simdType); | 
| 348 |             op1 = impSIMDPopStack(simdType); | 
| 349 |  | 
| 350 |             return gtNewSimdHWIntrinsicNode(simdType, op1, op2, intrinsic, simdBaseType, simdSizeBytes); | 
| 351 |  | 
| 352 |         case HWIntrinsicInfo::SimdTernaryRMWOp: | 
| 353 |         case HWIntrinsicInfo::SimdSelectOp: | 
| 354 |             // op1 is the first operand | 
| 355 |             // op2 is the second operand | 
| 356 |             // op3 is the third operand | 
| 357 |             op3 = impSIMDPopStack(simdType); | 
| 358 |             op2 = impSIMDPopStack(simdType); | 
| 359 |             op1 = impSIMDPopStack(simdType); | 
| 360 |  | 
| 361 |             return gtNewSimdHWIntrinsicNode(simdType, op1, op2, op3, intrinsic, simdBaseType, simdSizeBytes); | 
| 362 |  | 
| 363 |         case HWIntrinsicInfo::SimdSetAllOp: | 
| 364 |             op1 = impPopStack().val; | 
| 365 |  | 
| 366 |             return gtNewSimdHWIntrinsicNode(simdType, op1, intrinsic, simdBaseType, simdSizeBytes); | 
| 367 |  | 
| 368 |         case HWIntrinsicInfo::SimdUnaryOp: | 
| 369 |             op1 = impSIMDPopStack(simdType); | 
| 370 |  | 
| 371 |             return gtNewSimdHWIntrinsicNode(simdType, op1, intrinsic, simdBaseType, simdSizeBytes); | 
| 372 |  | 
| 373 |         case HWIntrinsicInfo::SimdExtractOp: | 
| 374 |             if (!mustExpand && !impCheckImmediate(impStackTop(0).val, getSIMDVectorLength(simdSizeBytes, simdBaseType))) | 
| 375 |             { | 
| 376 |                 // Immediate lane not constant or out of range | 
| 377 |                 return nullptr; | 
| 378 |             } | 
| 379 |             op2 = impPopStack().val; | 
| 380 |             op1 = impSIMDPopStack(simdType); | 
| 381 |  | 
| 382 |             return gtNewScalarHWIntrinsicNode(JITtype2varType(sig->retType), op1, op2, intrinsic); | 
| 383 |  | 
| 384 |         case HWIntrinsicInfo::SimdInsertOp: | 
| 385 |             if (!mustExpand && !impCheckImmediate(impStackTop(1).val, getSIMDVectorLength(simdSizeBytes, simdBaseType))) | 
| 386 |             { | 
| 387 |                 // Immediate lane not constant or out of range | 
| 388 |                 return nullptr; | 
| 389 |             } | 
| 390 |             op3 = impPopStack().val; | 
| 391 |             op2 = impPopStack().val; | 
| 392 |             op1 = impSIMDPopStack(simdType); | 
| 393 |  | 
| 394 |             return gtNewSimdHWIntrinsicNode(simdType, op1, op2, op3, intrinsic, simdBaseType, simdSizeBytes); | 
| 395 |  | 
| 396 |         case HWIntrinsicInfo::Sha1HashOp: | 
| 397 |             op3 = impSIMDPopStack(simdType); | 
| 398 |             op2 = impPopStack().val; | 
| 399 |             op1 = impSIMDPopStack(simdType); | 
| 400 |  | 
| 401 |             return gtNewSimdHWIntrinsicNode(simdType, op1, op2, op3, intrinsic, simdBaseType, simdSizeBytes); | 
| 402 |  | 
| 403 |         case HWIntrinsicInfo::Sha1RotateOp: | 
| 404 |             assert(sig->numArgs == 1); | 
| 405 |             compFloatingPointUsed = true; | 
| 406 |             return gtNewScalarHWIntrinsicNode(TYP_UINT, impPopStack().val, NI_ARM64_Sha1FixedRotate); | 
| 407 |  | 
| 408 |         default: | 
| 409 |             JITDUMP("Not implemented hardware intrinsic form" ); | 
| 410 |             assert(!"Unimplemented SIMD Intrinsic form" ); | 
| 411 |  | 
| 412 |             break; | 
| 413 |     } | 
| 414 |     return retNode; | 
| 415 | } | 
| 416 |  | 
| 417 | #endif // FEATURE_HW_INTRINSICS | 
| 418 |  |