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 | static const HWIntrinsicInfo hwIntrinsicInfoArray[] = { |
11 | // clang-format off |
12 | #define HARDWARE_INTRINSIC(id, name, isa, ival, size, numarg, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, category, flag) \ |
13 | {NI_##id, name, InstructionSet_##isa, ival, size, numarg, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, category, static_cast<HWIntrinsicFlag>(flag)}, |
14 | // clang-format on |
15 | #include "hwintrinsiclistxarch.h" |
16 | }; |
17 | |
18 | //------------------------------------------------------------------------ |
19 | // lookup: Gets the HWIntrinsicInfo associated with a given NamedIntrinsic |
20 | // |
21 | // Arguments: |
22 | // id -- The NamedIntrinsic associated with the HWIntrinsic to lookup |
23 | // |
24 | // Return Value: |
25 | // The HWIntrinsicInfo associated with id |
26 | const HWIntrinsicInfo& HWIntrinsicInfo::lookup(NamedIntrinsic id) |
27 | { |
28 | assert(id != NI_Illegal); |
29 | |
30 | assert(id > NI_HW_INTRINSIC_START); |
31 | assert(id < NI_HW_INTRINSIC_END); |
32 | |
33 | return hwIntrinsicInfoArray[id - NI_HW_INTRINSIC_START - 1]; |
34 | } |
35 | |
36 | //------------------------------------------------------------------------ |
37 | // lookupId: Gets the NamedIntrinsic for a given method name and InstructionSet |
38 | // |
39 | // Arguments: |
40 | // className -- The name of the class associated with the HWIntrinsic to lookup |
41 | // methodName -- The name of the method associated with the HWIntrinsic to lookup |
42 | // enclosingClassName -- The name of the enclosing class of X64 classes |
43 | // |
44 | // Return Value: |
45 | // The NamedIntrinsic associated with methodName and isa |
46 | NamedIntrinsic HWIntrinsicInfo::lookupId(const char* className, const char* methodName, const char* enclosingClassName) |
47 | { |
48 | // TODO-Throughput: replace sequential search by binary search |
49 | |
50 | InstructionSet isa = lookupIsa(className, enclosingClassName); |
51 | assert(isa != InstructionSet_ILLEGAL); |
52 | |
53 | assert(methodName != nullptr); |
54 | |
55 | for (int i = 0; i < (NI_HW_INTRINSIC_END - NI_HW_INTRINSIC_START - 1); i++) |
56 | { |
57 | if (isa != hwIntrinsicInfoArray[i].isa) |
58 | { |
59 | continue; |
60 | } |
61 | |
62 | if (strcmp(methodName, hwIntrinsicInfoArray[i].name) == 0) |
63 | { |
64 | return hwIntrinsicInfoArray[i].id; |
65 | } |
66 | } |
67 | |
68 | // There are several helper intrinsics that are implemented in managed code |
69 | // Those intrinsics will hit this code path and need to return NI_Illegal |
70 | return NI_Illegal; |
71 | } |
72 | |
73 | //------------------------------------------------------------------------ |
74 | // X64VersionOfIsa: Gets the corresponding 64-bit only InstructionSet for a given InstructionSet |
75 | // |
76 | // Arguments: |
77 | // isa -- The InstructionSet ID |
78 | // |
79 | // Return Value: |
80 | // The 64-bit only InstructionSet associated with isa |
81 | static InstructionSet X64VersionOfIsa(InstructionSet isa) |
82 | { |
83 | switch (isa) |
84 | { |
85 | case InstructionSet_SSE: |
86 | return InstructionSet_SSE_X64; |
87 | case InstructionSet_SSE2: |
88 | return InstructionSet_SSE2_X64; |
89 | case InstructionSet_SSE41: |
90 | return InstructionSet_SSE41_X64; |
91 | case InstructionSet_SSE42: |
92 | return InstructionSet_SSE42_X64; |
93 | case InstructionSet_BMI1: |
94 | return InstructionSet_BMI1_X64; |
95 | case InstructionSet_BMI2: |
96 | return InstructionSet_BMI2_X64; |
97 | case InstructionSet_LZCNT: |
98 | return InstructionSet_LZCNT_X64; |
99 | case InstructionSet_POPCNT: |
100 | return InstructionSet_POPCNT_X64; |
101 | default: |
102 | unreached(); |
103 | return InstructionSet_ILLEGAL; |
104 | } |
105 | } |
106 | |
107 | //------------------------------------------------------------------------ |
108 | // lookupInstructionSet: Gets the InstructionSet for a given class name |
109 | // |
110 | // Arguments: |
111 | // className -- The name of the class associated with the InstructionSet to lookup |
112 | // |
113 | // Return Value: |
114 | // The InstructionSet associated with className |
115 | static InstructionSet lookupInstructionSet(const char* className) |
116 | { |
117 | assert(className != nullptr); |
118 | if (className[0] == 'A') |
119 | { |
120 | if (strcmp(className, "Aes" ) == 0) |
121 | { |
122 | return InstructionSet_AES; |
123 | } |
124 | if (strcmp(className, "Avx" ) == 0) |
125 | { |
126 | return InstructionSet_AVX; |
127 | } |
128 | if (strcmp(className, "Avx2" ) == 0) |
129 | { |
130 | return InstructionSet_AVX2; |
131 | } |
132 | } |
133 | else if (className[0] == 'S') |
134 | { |
135 | if (strcmp(className, "Sse" ) == 0) |
136 | { |
137 | return InstructionSet_SSE; |
138 | } |
139 | if (strcmp(className, "Sse2" ) == 0) |
140 | { |
141 | return InstructionSet_SSE2; |
142 | } |
143 | if (strcmp(className, "Sse3" ) == 0) |
144 | { |
145 | return InstructionSet_SSE3; |
146 | } |
147 | if (strcmp(className, "Ssse3" ) == 0) |
148 | { |
149 | return InstructionSet_SSSE3; |
150 | } |
151 | if (strcmp(className, "Sse41" ) == 0) |
152 | { |
153 | return InstructionSet_SSE41; |
154 | } |
155 | if (strcmp(className, "Sse42" ) == 0) |
156 | { |
157 | return InstructionSet_SSE42; |
158 | } |
159 | } |
160 | else if (className[0] == 'B') |
161 | { |
162 | if (strcmp(className, "Bmi1" ) == 0) |
163 | { |
164 | return InstructionSet_BMI1; |
165 | } |
166 | if (strcmp(className, "Bmi2" ) == 0) |
167 | { |
168 | return InstructionSet_BMI2; |
169 | } |
170 | } |
171 | else if (className[0] == 'P') |
172 | { |
173 | if (strcmp(className, "Pclmulqdq" ) == 0) |
174 | { |
175 | return InstructionSet_PCLMULQDQ; |
176 | } |
177 | if (strcmp(className, "Popcnt" ) == 0) |
178 | { |
179 | return InstructionSet_POPCNT; |
180 | } |
181 | } |
182 | else if (strcmp(className, "Fma" ) == 0) |
183 | { |
184 | return InstructionSet_FMA; |
185 | } |
186 | else if (strcmp(className, "Lzcnt" ) == 0) |
187 | { |
188 | return InstructionSet_LZCNT; |
189 | } |
190 | |
191 | unreached(); |
192 | return InstructionSet_ILLEGAL; |
193 | } |
194 | |
195 | //------------------------------------------------------------------------ |
196 | // lookupIsa: Gets the InstructionSet for a given class name and enclsoing class name |
197 | // |
198 | // Arguments: |
199 | // className -- The name of the class associated with the InstructionSet to lookup |
200 | // enclosingClassName -- The name of the enclosing class of X64 classes |
201 | // |
202 | // Return Value: |
203 | // The InstructionSet associated with className and enclosingClassName |
204 | InstructionSet HWIntrinsicInfo::lookupIsa(const char* className, const char* enclosingClassName) |
205 | { |
206 | assert(className != nullptr); |
207 | |
208 | if (strcmp(className, "X64" ) == 0) |
209 | { |
210 | assert(enclosingClassName != nullptr); |
211 | return X64VersionOfIsa(lookupInstructionSet(enclosingClassName)); |
212 | } |
213 | else |
214 | { |
215 | return lookupInstructionSet(className); |
216 | } |
217 | } |
218 | |
219 | //------------------------------------------------------------------------ |
220 | // lookupSimdSize: Gets the SimdSize for a given HWIntrinsic and signature |
221 | // |
222 | // Arguments: |
223 | // id -- The ID associated with the HWIntrinsic to lookup |
224 | // sig -- The signature of the HWIntrinsic to lookup |
225 | // |
226 | // Return Value: |
227 | // The SIMD size for the HWIntrinsic associated with id and sig |
228 | // |
229 | // Remarks: |
230 | // This function is only used by the importer. After importation, we can |
231 | // get the SIMD size from the GenTreeHWIntrinsic node. |
232 | unsigned HWIntrinsicInfo::lookupSimdSize(Compiler* comp, NamedIntrinsic id, CORINFO_SIG_INFO* sig) |
233 | { |
234 | if (HWIntrinsicInfo::HasFixedSimdSize(id)) |
235 | { |
236 | return lookupSimdSize(id); |
237 | } |
238 | |
239 | CORINFO_CLASS_HANDLE typeHnd = nullptr; |
240 | |
241 | if (JITtype2varType(sig->retType) == TYP_STRUCT) |
242 | { |
243 | typeHnd = sig->retTypeSigClass; |
244 | } |
245 | else if (HWIntrinsicInfo::BaseTypeFromFirstArg(id)) |
246 | { |
247 | typeHnd = comp->info.compCompHnd->getArgClass(sig, sig->args); |
248 | } |
249 | else |
250 | { |
251 | assert(HWIntrinsicInfo::BaseTypeFromSecondArg(id)); |
252 | CORINFO_ARG_LIST_HANDLE secondArg = comp->info.compCompHnd->getArgNext(sig->args); |
253 | typeHnd = comp->info.compCompHnd->getArgClass(sig, secondArg); |
254 | } |
255 | |
256 | unsigned simdSize = 0; |
257 | var_types baseType = comp->getBaseTypeAndSizeOfSIMDType(typeHnd, &simdSize); |
258 | assert((simdSize > 0) && (baseType != TYP_UNKNOWN)); |
259 | return simdSize; |
260 | } |
261 | |
262 | //------------------------------------------------------------------------ |
263 | // lookupNumArgs: Gets the number of args for a given HWIntrinsic node |
264 | // |
265 | // Arguments: |
266 | // node -- The HWIntrinsic node to get the number of args for |
267 | // |
268 | // Return Value: |
269 | // The number of args for the HWIntrinsic associated with node |
270 | int HWIntrinsicInfo::lookupNumArgs(const GenTreeHWIntrinsic* node) |
271 | { |
272 | assert(node != nullptr); |
273 | |
274 | NamedIntrinsic id = node->gtHWIntrinsicId; |
275 | int numArgs = lookupNumArgs(id); |
276 | |
277 | if (numArgs >= 0) |
278 | { |
279 | return numArgs; |
280 | } |
281 | |
282 | assert(numArgs == -1); |
283 | |
284 | GenTree* op1 = node->gtGetOp1(); |
285 | |
286 | if (op1 == nullptr) |
287 | { |
288 | return 0; |
289 | } |
290 | |
291 | if (op1->OperIsList()) |
292 | { |
293 | GenTreeArgList* list = op1->AsArgList(); |
294 | numArgs = 0; |
295 | |
296 | do |
297 | { |
298 | numArgs++; |
299 | list = list->Rest(); |
300 | } while (list != nullptr); |
301 | |
302 | return numArgs; |
303 | } |
304 | |
305 | GenTree* op2 = node->gtGetOp2(); |
306 | |
307 | return (op2 == nullptr) ? 1 : 2; |
308 | } |
309 | |
310 | //------------------------------------------------------------------------ |
311 | // lookupLastOp: Gets the last operand for a given HWIntrinsic node |
312 | // |
313 | // Arguments: |
314 | // node -- The HWIntrinsic node to get the last operand for |
315 | // |
316 | // Return Value: |
317 | // The last operand for node |
318 | GenTree* HWIntrinsicInfo::lookupLastOp(const GenTreeHWIntrinsic* node) |
319 | { |
320 | int numArgs = lookupNumArgs(node); |
321 | |
322 | switch (numArgs) |
323 | { |
324 | case 0: |
325 | { |
326 | assert(node->gtGetOp1() == nullptr); |
327 | assert(node->gtGetOp2() == nullptr); |
328 | return nullptr; |
329 | } |
330 | |
331 | case 1: |
332 | { |
333 | assert(node->gtGetOp1() != nullptr); |
334 | assert(!node->gtGetOp1()->OperIsList()); |
335 | assert(node->gtGetOp2() == nullptr); |
336 | |
337 | return node->gtGetOp1(); |
338 | } |
339 | |
340 | case 2: |
341 | { |
342 | assert(node->gtGetOp1() != nullptr); |
343 | assert(!node->gtGetOp1()->OperIsList()); |
344 | assert(node->gtGetOp2() != nullptr); |
345 | |
346 | return node->gtGetOp2(); |
347 | } |
348 | |
349 | case 3: |
350 | { |
351 | assert(node->gtGetOp1() != nullptr); |
352 | assert(node->gtGetOp1()->OperIsList()); |
353 | assert(node->gtGetOp2() == nullptr); |
354 | assert(node->gtGetOp1()->AsArgList()->Rest()->Rest()->Current() != nullptr); |
355 | assert(node->gtGetOp1()->AsArgList()->Rest()->Rest()->Rest() == nullptr); |
356 | |
357 | return node->gtGetOp1()->AsArgList()->Rest()->Rest()->Current(); |
358 | } |
359 | |
360 | case 5: |
361 | { |
362 | assert(node->gtGetOp1() != nullptr); |
363 | assert(node->gtGetOp1()->OperIsList()); |
364 | assert(node->gtGetOp2() == nullptr); |
365 | assert(node->gtGetOp1()->AsArgList()->Rest()->Rest()->Rest()->Rest()->Current() != nullptr); |
366 | assert(node->gtGetOp1()->AsArgList()->Rest()->Rest()->Rest()->Rest()->Rest() == nullptr); |
367 | |
368 | return node->gtGetOp1()->AsArgList()->Rest()->Rest()->Rest()->Rest()->Current(); |
369 | } |
370 | |
371 | default: |
372 | { |
373 | unreached(); |
374 | return nullptr; |
375 | } |
376 | } |
377 | } |
378 | |
379 | //------------------------------------------------------------------------ |
380 | // isImmOp: Gets a value that indicates whether the HWIntrinsic node has an imm operand |
381 | // |
382 | // Arguments: |
383 | // id -- The NamedIntrinsic associated with the HWIntrinsic to lookup |
384 | // op -- The operand to check |
385 | // |
386 | // Return Value: |
387 | // true if the node has an imm operand; otherwise, false |
388 | bool HWIntrinsicInfo::isImmOp(NamedIntrinsic id, const GenTree* op) |
389 | { |
390 | if (HWIntrinsicInfo::lookupCategory(id) != HW_Category_IMM) |
391 | { |
392 | return false; |
393 | } |
394 | |
395 | if (!HWIntrinsicInfo::MaybeImm(id)) |
396 | { |
397 | return true; |
398 | } |
399 | |
400 | if (genActualType(op->TypeGet()) != TYP_INT) |
401 | { |
402 | return false; |
403 | } |
404 | |
405 | return true; |
406 | } |
407 | |
408 | //------------------------------------------------------------------------ |
409 | // lookupImmUpperBound: Gets the upper bound for the imm-value of a given NamedIntrinsic |
410 | // |
411 | // Arguments: |
412 | // id -- The NamedIntrinsic associated with the HWIntrinsic to lookup |
413 | // |
414 | // Return Value: |
415 | // The upper bound for the imm-value of the intrinsic associated with id |
416 | // |
417 | int HWIntrinsicInfo::lookupImmUpperBound(NamedIntrinsic id) |
418 | { |
419 | assert(HWIntrinsicInfo::lookupCategory(id) == HW_Category_IMM); |
420 | |
421 | switch (id) |
422 | { |
423 | case NI_AVX_Compare: |
424 | case NI_AVX_CompareScalar: |
425 | { |
426 | assert(!HWIntrinsicInfo::HasFullRangeImm(id)); |
427 | return 31; // enum FloatComparisonMode has 32 values |
428 | } |
429 | |
430 | case NI_AVX2_GatherVector128: |
431 | case NI_AVX2_GatherVector256: |
432 | case NI_AVX2_GatherMaskVector128: |
433 | case NI_AVX2_GatherMaskVector256: |
434 | return 8; |
435 | |
436 | default: |
437 | { |
438 | assert(HWIntrinsicInfo::HasFullRangeImm(id)); |
439 | return 255; |
440 | } |
441 | } |
442 | } |
443 | |
444 | //------------------------------------------------------------------------ |
445 | // isInImmRange: Check if ival is valid for the intrinsic |
446 | // |
447 | // Arguments: |
448 | // id -- The NamedIntrinsic associated with the HWIntrinsic to lookup |
449 | // ival -- the imm value to be checked |
450 | // |
451 | // Return Value: |
452 | // true if ival is valid for the intrinsic |
453 | // |
454 | bool HWIntrinsicInfo::isInImmRange(NamedIntrinsic id, int ival) |
455 | { |
456 | assert(HWIntrinsicInfo::lookupCategory(id) == HW_Category_IMM); |
457 | |
458 | if (isAVX2GatherIntrinsic(id)) |
459 | { |
460 | return ival == 1 || ival == 2 || ival == 4 || ival == 8; |
461 | } |
462 | else |
463 | { |
464 | return ival <= lookupImmUpperBound(id) && ival >= 0; |
465 | } |
466 | } |
467 | |
468 | //------------------------------------------------------------------------ |
469 | // isAVX2GatherIntrinsic: Check if the intrinsic is AVX Gather* |
470 | // |
471 | // Arguments: |
472 | // id -- The NamedIntrinsic associated with the HWIntrinsic to lookup |
473 | // |
474 | // Return Value: |
475 | // true if id is AVX Gather* intrinsic |
476 | // |
477 | bool HWIntrinsicInfo::isAVX2GatherIntrinsic(NamedIntrinsic id) |
478 | { |
479 | switch (id) |
480 | { |
481 | case NI_AVX2_GatherVector128: |
482 | case NI_AVX2_GatherVector256: |
483 | case NI_AVX2_GatherMaskVector128: |
484 | case NI_AVX2_GatherMaskVector256: |
485 | return true; |
486 | default: |
487 | return false; |
488 | } |
489 | } |
490 | |
491 | //------------------------------------------------------------------------ |
492 | // isFullyImplementedIsa: Gets a value that indicates whether the InstructionSet is fully implemented |
493 | // |
494 | // Arguments: |
495 | // isa - The InstructionSet to check |
496 | // |
497 | // Return Value: |
498 | // true if isa is supported; otherwise, false |
499 | bool HWIntrinsicInfo::isFullyImplementedIsa(InstructionSet isa) |
500 | { |
501 | switch (isa) |
502 | { |
503 | // These ISAs are fully implemented |
504 | case InstructionSet_AES: |
505 | case InstructionSet_AVX: |
506 | case InstructionSet_AVX2: |
507 | case InstructionSet_BMI1: |
508 | case InstructionSet_BMI2: |
509 | case InstructionSet_BMI1_X64: |
510 | case InstructionSet_BMI2_X64: |
511 | case InstructionSet_FMA: |
512 | case InstructionSet_LZCNT: |
513 | case InstructionSet_LZCNT_X64: |
514 | case InstructionSet_PCLMULQDQ: |
515 | case InstructionSet_POPCNT: |
516 | case InstructionSet_POPCNT_X64: |
517 | case InstructionSet_SSE: |
518 | case InstructionSet_SSE_X64: |
519 | case InstructionSet_SSE2: |
520 | case InstructionSet_SSE2_X64: |
521 | case InstructionSet_SSE3: |
522 | case InstructionSet_SSSE3: |
523 | case InstructionSet_SSE41: |
524 | case InstructionSet_SSE41_X64: |
525 | case InstructionSet_SSE42: |
526 | case InstructionSet_SSE42_X64: |
527 | { |
528 | return true; |
529 | } |
530 | |
531 | default: |
532 | { |
533 | unreached(); |
534 | } |
535 | } |
536 | } |
537 | |
538 | //------------------------------------------------------------------------ |
539 | // isScalarIsa: Gets a value that indicates whether the InstructionSet is scalar |
540 | // |
541 | // Arguments: |
542 | // isa - The InstructionSet to check |
543 | // |
544 | // Return Value: |
545 | // true if isa is scalar; otherwise, false |
546 | bool HWIntrinsicInfo::isScalarIsa(InstructionSet isa) |
547 | { |
548 | switch (isa) |
549 | { |
550 | case InstructionSet_BMI1: |
551 | case InstructionSet_BMI2: |
552 | case InstructionSet_BMI1_X64: |
553 | case InstructionSet_BMI2_X64: |
554 | case InstructionSet_LZCNT: |
555 | case InstructionSet_LZCNT_X64: |
556 | case InstructionSet_POPCNT: |
557 | case InstructionSet_POPCNT_X64: |
558 | { |
559 | return true; |
560 | } |
561 | |
562 | default: |
563 | { |
564 | return false; |
565 | } |
566 | } |
567 | } |
568 | |
569 | //------------------------------------------------------------------------ |
570 | // getArgForHWIntrinsic: get the argument from the stack and match the signature |
571 | // |
572 | // Arguments: |
573 | // argType -- the required type of argument |
574 | // argClass -- the class handle of argType |
575 | // |
576 | // Return Value: |
577 | // get the argument at the given index from the stack and match the signature |
578 | // |
579 | GenTree* Compiler::getArgForHWIntrinsic(var_types argType, CORINFO_CLASS_HANDLE argClass) |
580 | { |
581 | GenTree* arg = nullptr; |
582 | if (argType == TYP_STRUCT) |
583 | { |
584 | unsigned int argSizeBytes; |
585 | var_types base = getBaseTypeAndSizeOfSIMDType(argClass, &argSizeBytes); |
586 | argType = getSIMDTypeForSize(argSizeBytes); |
587 | assert((argType == TYP_SIMD32) || (argType == TYP_SIMD16)); |
588 | arg = impSIMDPopStack(argType); |
589 | assert((arg->TypeGet() == TYP_SIMD16) || (arg->TypeGet() == TYP_SIMD32)); |
590 | } |
591 | else |
592 | { |
593 | assert(varTypeIsArithmetic(argType)); |
594 | arg = impPopStack().val; |
595 | assert(varTypeIsArithmetic(arg->TypeGet())); |
596 | assert(genActualType(arg->gtType) == genActualType(argType)); |
597 | } |
598 | return arg; |
599 | } |
600 | |
601 | //------------------------------------------------------------------------ |
602 | // impNonConstFallback: convert certain SSE2/AVX2 shift intrinsic to its semantic alternative when the imm-arg is |
603 | // not a compile-time constant |
604 | // |
605 | // Arguments: |
606 | // intrinsic -- intrinsic ID |
607 | // simdType -- Vector type |
608 | // baseType -- base type of the Vector128/256<T> |
609 | // |
610 | // Return Value: |
611 | // return the IR of semantic alternative on non-const imm-arg |
612 | // |
613 | GenTree* Compiler::impNonConstFallback(NamedIntrinsic intrinsic, var_types simdType, var_types baseType) |
614 | { |
615 | assert(HWIntrinsicInfo::NoJmpTableImm(intrinsic)); |
616 | switch (intrinsic) |
617 | { |
618 | case NI_SSE2_ShiftLeftLogical: |
619 | case NI_SSE2_ShiftRightArithmetic: |
620 | case NI_SSE2_ShiftRightLogical: |
621 | case NI_AVX2_ShiftLeftLogical: |
622 | case NI_AVX2_ShiftRightArithmetic: |
623 | case NI_AVX2_ShiftRightLogical: |
624 | { |
625 | GenTree* op2 = impPopStack().val; |
626 | GenTree* op1 = impSIMDPopStack(simdType); |
627 | GenTree* tmpOp = |
628 | gtNewSimdHWIntrinsicNode(TYP_SIMD16, op2, NI_SSE2_ConvertScalarToVector128Int32, TYP_INT, 16); |
629 | return gtNewSimdHWIntrinsicNode(simdType, op1, tmpOp, intrinsic, baseType, genTypeSize(simdType)); |
630 | } |
631 | |
632 | default: |
633 | unreached(); |
634 | return nullptr; |
635 | } |
636 | } |
637 | |
638 | //------------------------------------------------------------------------ |
639 | // addRangeCheckIfNeeded: add a GT_HW_INTRINSIC_CHK node for non-full-range imm-intrinsic |
640 | // |
641 | // Arguments: |
642 | // intrinsic -- intrinsic ID |
643 | // lastOp -- the last operand of the intrinsic that points to the imm-arg |
644 | // mustExpand -- true if the compiler is compiling the fallback(GT_CALL) of this intrinsics |
645 | // |
646 | // Return Value: |
647 | // add a GT_HW_INTRINSIC_CHK node for non-full-range imm-intrinsic, which would throw ArgumentOutOfRangeException |
648 | // when the imm-argument is not in the valid range |
649 | // |
650 | GenTree* Compiler::addRangeCheckIfNeeded(NamedIntrinsic intrinsic, GenTree* lastOp, bool mustExpand) |
651 | { |
652 | assert(lastOp != nullptr); |
653 | // Full-range imm-intrinsics do not need the range-check |
654 | // because the imm-parameter of the intrinsic method is a byte. |
655 | // AVX2 Gather intrinsics no not need the range-check |
656 | // because their imm-parameter have discrete valid values that are handle by managed code |
657 | if (mustExpand && !HWIntrinsicInfo::HasFullRangeImm(intrinsic) && HWIntrinsicInfo::isImmOp(intrinsic, lastOp) && |
658 | !HWIntrinsicInfo::isAVX2GatherIntrinsic(intrinsic)) |
659 | { |
660 | assert(!lastOp->IsCnsIntOrI()); |
661 | GenTree* upperBoundNode = |
662 | new (this, GT_CNS_INT) GenTreeIntCon(TYP_INT, HWIntrinsicInfo::lookupImmUpperBound(intrinsic)); |
663 | GenTree* index = nullptr; |
664 | if ((lastOp->gtFlags & GTF_SIDE_EFFECT) != 0) |
665 | { |
666 | index = fgInsertCommaFormTemp(&lastOp); |
667 | } |
668 | else |
669 | { |
670 | index = gtCloneExpr(lastOp); |
671 | } |
672 | GenTreeBoundsChk* hwIntrinsicChk = new (this, GT_HW_INTRINSIC_CHK) |
673 | GenTreeBoundsChk(GT_HW_INTRINSIC_CHK, TYP_VOID, index, upperBoundNode, SCK_RNGCHK_FAIL); |
674 | hwIntrinsicChk->gtThrowKind = SCK_ARG_RNG_EXCPN; |
675 | return gtNewOperNode(GT_COMMA, lastOp->TypeGet(), hwIntrinsicChk, lastOp); |
676 | } |
677 | else |
678 | { |
679 | return lastOp; |
680 | } |
681 | } |
682 | |
683 | //------------------------------------------------------------------------ |
684 | // compSupportsHWIntrinsic: compiler support of hardware intrinsics |
685 | // |
686 | // Arguments: |
687 | // isa - Instruction set |
688 | // Return Value: |
689 | // true if |
690 | // - isa is a scalar ISA |
691 | // - isa is a SIMD ISA and featureSIMD=true |
692 | // - isa is fully implemented or EnableIncompleteISAClass=true |
693 | bool Compiler::compSupportsHWIntrinsic(InstructionSet isa) |
694 | { |
695 | return (featureSIMD || HWIntrinsicInfo::isScalarIsa(isa)) && ( |
696 | #ifdef DEBUG |
697 | JitConfig.EnableIncompleteISAClass() || |
698 | #endif |
699 | HWIntrinsicInfo::isFullyImplementedIsa(isa)); |
700 | } |
701 | |
702 | //------------------------------------------------------------------------ |
703 | // impIsTableDrivenHWIntrinsic: |
704 | // |
705 | // Arguments: |
706 | // category - category of a HW intrinsic |
707 | // |
708 | // Return Value: |
709 | // returns true if this category can be table-driven in the importer |
710 | // |
711 | static bool impIsTableDrivenHWIntrinsic(NamedIntrinsic intrinsicId, HWIntrinsicCategory category) |
712 | { |
713 | // HW_Flag_NoCodeGen implies this intrinsic should be manually morphed in the importer. |
714 | return (category != HW_Category_Special) && (category != HW_Category_Scalar) && |
715 | HWIntrinsicInfo::RequiresCodegen(intrinsicId) && !HWIntrinsicInfo::HasSpecialImport(intrinsicId); |
716 | } |
717 | |
718 | //------------------------------------------------------------------------ |
719 | // impHWIntrinsic: dispatch hardware intrinsics to their own implementation |
720 | // |
721 | // Arguments: |
722 | // intrinsic -- id of the intrinsic function. |
723 | // method -- method handle of the intrinsic function. |
724 | // sig -- signature of the intrinsic call |
725 | // |
726 | // Return Value: |
727 | // the expanded intrinsic. |
728 | // |
729 | GenTree* Compiler::impHWIntrinsic(NamedIntrinsic intrinsic, |
730 | CORINFO_METHOD_HANDLE method, |
731 | CORINFO_SIG_INFO* sig, |
732 | bool mustExpand) |
733 | { |
734 | InstructionSet isa = HWIntrinsicInfo::lookupIsa(intrinsic); |
735 | HWIntrinsicCategory category = HWIntrinsicInfo::lookupCategory(intrinsic); |
736 | int numArgs = sig->numArgs; |
737 | var_types retType = JITtype2varType(sig->retType); |
738 | var_types baseType = TYP_UNKNOWN; |
739 | |
740 | if ((retType == TYP_STRUCT) && featureSIMD) |
741 | { |
742 | unsigned int sizeBytes; |
743 | baseType = getBaseTypeAndSizeOfSIMDType(sig->retTypeSigClass, &sizeBytes); |
744 | retType = getSIMDTypeForSize(sizeBytes); |
745 | assert(sizeBytes != 0); |
746 | } |
747 | |
748 | // This intrinsic is supported if |
749 | // - the ISA is available on the underlying hardware (compSupports returns true) |
750 | // - the compiler supports this hardware intrinsics (compSupportsHWIntrinsic returns true) |
751 | bool issupported = compSupports(isa) && compSupportsHWIntrinsic(isa); |
752 | |
753 | if (category == HW_Category_IsSupportedProperty) |
754 | { |
755 | return gtNewIconNode(issupported); |
756 | } |
757 | // - calling to unsupported intrinsics must throw PlatforNotSupportedException |
758 | else if (!issupported) |
759 | { |
760 | return impUnsupportedHWIntrinsic(CORINFO_HELP_THROW_PLATFORM_NOT_SUPPORTED, method, sig, mustExpand); |
761 | } |
762 | // Avoid checking stacktop for 0-op intrinsics |
763 | if (sig->numArgs > 0 && HWIntrinsicInfo::isImmOp(intrinsic, impStackTop().val)) |
764 | { |
765 | GenTree* lastOp = impStackTop().val; |
766 | // The imm-HWintrinsics that do not accept all imm8 values may throw |
767 | // ArgumentOutOfRangeException when the imm argument is not in the valid range |
768 | if (!HWIntrinsicInfo::HasFullRangeImm(intrinsic)) |
769 | { |
770 | if (!mustExpand && lastOp->IsCnsIntOrI() && |
771 | !HWIntrinsicInfo::isInImmRange(intrinsic, (int)lastOp->AsIntCon()->IconValue())) |
772 | { |
773 | return nullptr; |
774 | } |
775 | } |
776 | |
777 | if (!lastOp->IsCnsIntOrI()) |
778 | { |
779 | if (HWIntrinsicInfo::NoJmpTableImm(intrinsic)) |
780 | { |
781 | return impNonConstFallback(intrinsic, retType, baseType); |
782 | } |
783 | |
784 | if (!mustExpand) |
785 | { |
786 | // When the imm-argument is not a constant and we are not being forced to expand, we need to |
787 | // return nullptr so a GT_CALL to the intrinsic method is emitted instead. The |
788 | // intrinsic method is recursive and will be forced to expand, at which point |
789 | // we emit some less efficient fallback code. |
790 | return nullptr; |
791 | } |
792 | } |
793 | } |
794 | |
795 | bool isTableDriven = impIsTableDrivenHWIntrinsic(intrinsic, category); |
796 | |
797 | if (isTableDriven && ((category == HW_Category_MemoryStore) || HWIntrinsicInfo::BaseTypeFromFirstArg(intrinsic) || |
798 | HWIntrinsicInfo::BaseTypeFromSecondArg(intrinsic))) |
799 | { |
800 | if (HWIntrinsicInfo::BaseTypeFromFirstArg(intrinsic)) |
801 | { |
802 | baseType = getBaseTypeOfSIMDType(info.compCompHnd->getArgClass(sig, sig->args)); |
803 | } |
804 | else |
805 | { |
806 | assert((category == HW_Category_MemoryStore) || HWIntrinsicInfo::BaseTypeFromSecondArg(intrinsic)); |
807 | CORINFO_ARG_LIST_HANDLE secondArg = info.compCompHnd->getArgNext(sig->args); |
808 | CORINFO_CLASS_HANDLE secondArgClass = info.compCompHnd->getArgClass(sig, secondArg); |
809 | baseType = getBaseTypeOfSIMDType(secondArgClass); |
810 | |
811 | if (baseType == TYP_UNKNOWN) // the second argument is not a vector |
812 | { |
813 | baseType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, secondArg, &secondArgClass))); |
814 | } |
815 | } |
816 | } |
817 | |
818 | if (HWIntrinsicInfo::IsFloatingPointUsed(intrinsic)) |
819 | { |
820 | // Set `compFloatingPointUsed` to cover the scenario where an intrinsic is being on SIMD fields, but |
821 | // where no SIMD local vars are in use. This is the same logic as is used for FEATURE_SIMD. |
822 | compFloatingPointUsed = true; |
823 | } |
824 | |
825 | // table-driven importer of simple intrinsics |
826 | if (isTableDriven) |
827 | { |
828 | unsigned simdSize = HWIntrinsicInfo::lookupSimdSize(this, intrinsic, sig); |
829 | CORINFO_ARG_LIST_HANDLE argList = sig->args; |
830 | CORINFO_CLASS_HANDLE argClass; |
831 | var_types argType = TYP_UNKNOWN; |
832 | |
833 | assert(numArgs >= 0); |
834 | assert(HWIntrinsicInfo::lookupIns(intrinsic, baseType) != INS_invalid); |
835 | assert(simdSize == 32 || simdSize == 16); |
836 | |
837 | GenTreeHWIntrinsic* retNode = nullptr; |
838 | GenTree* op1 = nullptr; |
839 | GenTree* op2 = nullptr; |
840 | |
841 | switch (numArgs) |
842 | { |
843 | case 0: |
844 | retNode = gtNewSimdHWIntrinsicNode(retType, intrinsic, baseType, simdSize); |
845 | break; |
846 | case 1: |
847 | argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); |
848 | op1 = getArgForHWIntrinsic(argType, argClass); |
849 | retNode = gtNewSimdHWIntrinsicNode(retType, op1, intrinsic, baseType, simdSize); |
850 | break; |
851 | case 2: |
852 | argType = JITtype2varType( |
853 | strip(info.compCompHnd->getArgType(sig, info.compCompHnd->getArgNext(argList), &argClass))); |
854 | op2 = getArgForHWIntrinsic(argType, argClass); |
855 | |
856 | op2 = addRangeCheckIfNeeded(intrinsic, op2, mustExpand); |
857 | |
858 | argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); |
859 | op1 = getArgForHWIntrinsic(argType, argClass); |
860 | |
861 | retNode = gtNewSimdHWIntrinsicNode(retType, op1, op2, intrinsic, baseType, simdSize); |
862 | break; |
863 | |
864 | case 3: |
865 | { |
866 | CORINFO_ARG_LIST_HANDLE arg2 = info.compCompHnd->getArgNext(argList); |
867 | CORINFO_ARG_LIST_HANDLE arg3 = info.compCompHnd->getArgNext(arg2); |
868 | |
869 | argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg3, &argClass))); |
870 | GenTree* op3 = getArgForHWIntrinsic(argType, argClass); |
871 | |
872 | op3 = addRangeCheckIfNeeded(intrinsic, op3, mustExpand); |
873 | |
874 | argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg2, &argClass))); |
875 | op2 = getArgForHWIntrinsic(argType, argClass); |
876 | var_types op2Type; |
877 | if (intrinsic == NI_AVX2_GatherVector128 || intrinsic == NI_AVX2_GatherVector256) |
878 | { |
879 | assert(varTypeIsSIMD(op2->TypeGet())); |
880 | op2Type = getBaseTypeOfSIMDType(argClass); |
881 | } |
882 | |
883 | argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); |
884 | op1 = getArgForHWIntrinsic(argType, argClass); |
885 | |
886 | retNode = gtNewSimdHWIntrinsicNode(retType, op1, op2, op3, intrinsic, baseType, simdSize); |
887 | |
888 | if (intrinsic == NI_AVX2_GatherVector128 || intrinsic == NI_AVX2_GatherVector256) |
889 | { |
890 | assert(varTypeIsSIMD(op2->TypeGet())); |
891 | retNode->AsHWIntrinsic()->gtIndexBaseType = op2Type; |
892 | } |
893 | break; |
894 | } |
895 | |
896 | default: |
897 | unreached(); |
898 | } |
899 | |
900 | bool isMemoryStore = retNode->OperIsMemoryStore(); |
901 | if (isMemoryStore || retNode->OperIsMemoryLoad()) |
902 | { |
903 | if (isMemoryStore) |
904 | { |
905 | // A MemoryStore operation is an assignment |
906 | retNode->gtFlags |= GTF_ASG; |
907 | } |
908 | |
909 | // This operation contains an implicit indirection |
910 | // it could point into the gloabal heap or |
911 | // it could throw a null reference exception. |
912 | // |
913 | retNode->gtFlags |= (GTF_GLOB_REF | GTF_EXCEPT); |
914 | } |
915 | return retNode; |
916 | } |
917 | |
918 | // other intrinsics need special importation |
919 | switch (isa) |
920 | { |
921 | case InstructionSet_SSE: |
922 | return impSSEIntrinsic(intrinsic, method, sig, mustExpand); |
923 | case InstructionSet_SSE2: |
924 | return impSSE2Intrinsic(intrinsic, method, sig, mustExpand); |
925 | case InstructionSet_SSE42: |
926 | case InstructionSet_SSE42_X64: |
927 | return impSSE42Intrinsic(intrinsic, method, sig, mustExpand); |
928 | case InstructionSet_AVX: |
929 | case InstructionSet_AVX2: |
930 | return impAvxOrAvx2Intrinsic(intrinsic, method, sig, mustExpand); |
931 | |
932 | case InstructionSet_AES: |
933 | return impAESIntrinsic(intrinsic, method, sig, mustExpand); |
934 | case InstructionSet_BMI1: |
935 | case InstructionSet_BMI1_X64: |
936 | case InstructionSet_BMI2: |
937 | case InstructionSet_BMI2_X64: |
938 | return impBMI1OrBMI2Intrinsic(intrinsic, method, sig, mustExpand); |
939 | |
940 | case InstructionSet_FMA: |
941 | return impFMAIntrinsic(intrinsic, method, sig, mustExpand); |
942 | case InstructionSet_LZCNT: |
943 | case InstructionSet_LZCNT_X64: |
944 | return impLZCNTIntrinsic(intrinsic, method, sig, mustExpand); |
945 | case InstructionSet_PCLMULQDQ: |
946 | return impPCLMULQDQIntrinsic(intrinsic, method, sig, mustExpand); |
947 | case InstructionSet_POPCNT: |
948 | case InstructionSet_POPCNT_X64: |
949 | return impPOPCNTIntrinsic(intrinsic, method, sig, mustExpand); |
950 | default: |
951 | return nullptr; |
952 | } |
953 | } |
954 | |
955 | GenTree* Compiler::impSSEIntrinsic(NamedIntrinsic intrinsic, |
956 | CORINFO_METHOD_HANDLE method, |
957 | CORINFO_SIG_INFO* sig, |
958 | bool mustExpand) |
959 | { |
960 | GenTree* retNode = nullptr; |
961 | GenTree* op1 = nullptr; |
962 | GenTree* op2 = nullptr; |
963 | GenTree* op3 = nullptr; |
964 | GenTree* op4 = nullptr; |
965 | int simdSize = HWIntrinsicInfo::lookupSimdSize(this, intrinsic, sig); |
966 | |
967 | // The Prefetch and StoreFence intrinsics don't take any SIMD operands |
968 | // and have a simdSize of 0 |
969 | assert((simdSize == 16) || (simdSize == 0)); |
970 | |
971 | switch (intrinsic) |
972 | { |
973 | case NI_SSE_MoveMask: |
974 | assert(sig->numArgs == 1); |
975 | assert(JITtype2varType(sig->retType) == TYP_INT); |
976 | assert(getBaseTypeOfSIMDType(info.compCompHnd->getArgClass(sig, sig->args)) == TYP_FLOAT); |
977 | op1 = impSIMDPopStack(TYP_SIMD16); |
978 | retNode = gtNewSimdHWIntrinsicNode(TYP_INT, op1, intrinsic, TYP_FLOAT, simdSize); |
979 | break; |
980 | |
981 | case NI_SSE_Prefetch0: |
982 | case NI_SSE_Prefetch1: |
983 | case NI_SSE_Prefetch2: |
984 | case NI_SSE_PrefetchNonTemporal: |
985 | { |
986 | assert(sig->numArgs == 1); |
987 | assert(JITtype2varType(sig->retType) == TYP_VOID); |
988 | op1 = impPopStack().val; |
989 | retNode = gtNewSimdHWIntrinsicNode(TYP_VOID, op1, intrinsic, TYP_UBYTE, 0); |
990 | break; |
991 | } |
992 | |
993 | case NI_SSE_StoreFence: |
994 | assert(sig->numArgs == 0); |
995 | assert(JITtype2varType(sig->retType) == TYP_VOID); |
996 | retNode = gtNewSimdHWIntrinsicNode(TYP_VOID, intrinsic, TYP_VOID, 0); |
997 | break; |
998 | |
999 | default: |
1000 | JITDUMP("Not implemented hardware intrinsic" ); |
1001 | break; |
1002 | } |
1003 | return retNode; |
1004 | } |
1005 | |
1006 | GenTree* Compiler::impSSE2Intrinsic(NamedIntrinsic intrinsic, |
1007 | CORINFO_METHOD_HANDLE method, |
1008 | CORINFO_SIG_INFO* sig, |
1009 | bool mustExpand) |
1010 | { |
1011 | GenTree* retNode = nullptr; |
1012 | GenTree* op1 = nullptr; |
1013 | GenTree* op2 = nullptr; |
1014 | int ival = -1; |
1015 | int simdSize = HWIntrinsicInfo::lookupSimdSize(this, intrinsic, sig); |
1016 | var_types baseType = TYP_UNKNOWN; |
1017 | var_types retType = TYP_UNKNOWN; |
1018 | |
1019 | // The fencing intrinsics don't take any operands and simdSize is 0 |
1020 | assert((simdSize == 16) || (simdSize == 0)); |
1021 | |
1022 | CORINFO_ARG_LIST_HANDLE argList = sig->args; |
1023 | var_types argType = TYP_UNKNOWN; |
1024 | |
1025 | switch (intrinsic) |
1026 | { |
1027 | case NI_SSE2_CompareLessThan: |
1028 | { |
1029 | assert(sig->numArgs == 2); |
1030 | op2 = impSIMDPopStack(TYP_SIMD16); |
1031 | op1 = impSIMDPopStack(TYP_SIMD16); |
1032 | baseType = getBaseTypeOfSIMDType(sig->retTypeSigClass); |
1033 | if (baseType == TYP_DOUBLE) |
1034 | { |
1035 | retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1, op2, intrinsic, baseType, simdSize); |
1036 | } |
1037 | else |
1038 | { |
1039 | retNode = |
1040 | gtNewSimdHWIntrinsicNode(TYP_SIMD16, op2, op1, NI_SSE2_CompareGreaterThan, baseType, simdSize); |
1041 | } |
1042 | break; |
1043 | } |
1044 | |
1045 | case NI_SSE2_LoadFence: |
1046 | case NI_SSE2_MemoryFence: |
1047 | { |
1048 | assert(sig->numArgs == 0); |
1049 | assert(JITtype2varType(sig->retType) == TYP_VOID); |
1050 | assert(simdSize == 0); |
1051 | |
1052 | retNode = gtNewSimdHWIntrinsicNode(TYP_VOID, intrinsic, TYP_VOID, simdSize); |
1053 | break; |
1054 | } |
1055 | |
1056 | case NI_SSE2_MoveMask: |
1057 | { |
1058 | assert(sig->numArgs == 1); |
1059 | retType = JITtype2varType(sig->retType); |
1060 | assert(retType == TYP_INT); |
1061 | op1 = impSIMDPopStack(TYP_SIMD16); |
1062 | baseType = getBaseTypeOfSIMDType(info.compCompHnd->getArgClass(sig, sig->args)); |
1063 | retNode = gtNewSimdHWIntrinsicNode(retType, op1, intrinsic, baseType, simdSize); |
1064 | break; |
1065 | } |
1066 | |
1067 | case NI_SSE2_StoreNonTemporal: |
1068 | { |
1069 | assert(sig->numArgs == 2); |
1070 | assert(JITtype2varType(sig->retType) == TYP_VOID); |
1071 | op2 = impPopStack().val; |
1072 | op1 = impPopStack().val; |
1073 | retNode = gtNewSimdHWIntrinsicNode(TYP_VOID, op1, op2, NI_SSE2_StoreNonTemporal, op2->TypeGet(), 0); |
1074 | break; |
1075 | } |
1076 | |
1077 | default: |
1078 | JITDUMP("Not implemented hardware intrinsic" ); |
1079 | break; |
1080 | } |
1081 | return retNode; |
1082 | } |
1083 | |
1084 | GenTree* Compiler::impSSE42Intrinsic(NamedIntrinsic intrinsic, |
1085 | CORINFO_METHOD_HANDLE method, |
1086 | CORINFO_SIG_INFO* sig, |
1087 | bool mustExpand) |
1088 | { |
1089 | GenTree* retNode = nullptr; |
1090 | GenTree* op1 = nullptr; |
1091 | GenTree* op2 = nullptr; |
1092 | var_types callType = JITtype2varType(sig->retType); |
1093 | |
1094 | CORINFO_ARG_LIST_HANDLE argList = sig->args; |
1095 | CORINFO_CLASS_HANDLE argClass; |
1096 | CorInfoType corType; |
1097 | switch (intrinsic) |
1098 | { |
1099 | case NI_SSE42_Crc32: |
1100 | case NI_SSE42_X64_Crc32: |
1101 | assert(sig->numArgs == 2); |
1102 | op2 = impPopStack().val; |
1103 | op1 = impPopStack().val; |
1104 | argList = info.compCompHnd->getArgNext(argList); // the second argument |
1105 | corType = strip(info.compCompHnd->getArgType(sig, argList, &argClass)); // type of the second argument |
1106 | |
1107 | retNode = gtNewScalarHWIntrinsicNode(callType, op1, op2, intrinsic); |
1108 | |
1109 | // TODO - currently we use the BaseType to bring the type of the second argument |
1110 | // to the code generator. May encode the overload info in other way. |
1111 | retNode->gtHWIntrinsic.gtSIMDBaseType = JITtype2varType(corType); |
1112 | break; |
1113 | |
1114 | default: |
1115 | JITDUMP("Not implemented hardware intrinsic" ); |
1116 | break; |
1117 | } |
1118 | return retNode; |
1119 | } |
1120 | |
1121 | GenTree* Compiler::impAvxOrAvx2Intrinsic(NamedIntrinsic intrinsic, |
1122 | CORINFO_METHOD_HANDLE method, |
1123 | CORINFO_SIG_INFO* sig, |
1124 | bool mustExpand) |
1125 | { |
1126 | GenTree* retNode = nullptr; |
1127 | GenTree* op1 = nullptr; |
1128 | GenTree* op2 = nullptr; |
1129 | var_types baseType = TYP_UNKNOWN; |
1130 | int simdSize = HWIntrinsicInfo::lookupSimdSize(this, intrinsic, sig); |
1131 | |
1132 | switch (intrinsic) |
1133 | { |
1134 | case NI_AVX_ExtractVector128: |
1135 | case NI_AVX2_ExtractVector128: |
1136 | { |
1137 | GenTree* lastOp = impPopStack().val; |
1138 | assert(lastOp->IsCnsIntOrI() || mustExpand); |
1139 | GenTree* vectorOp = impSIMDPopStack(TYP_SIMD32); |
1140 | if (sig->numArgs == 2) |
1141 | { |
1142 | baseType = getBaseTypeOfSIMDType(sig->retTypeSigClass); |
1143 | retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, vectorOp, lastOp, intrinsic, baseType, 32); |
1144 | } |
1145 | else |
1146 | { |
1147 | assert(sig->numArgs == 3); |
1148 | op1 = impPopStack().val; |
1149 | CORINFO_ARG_LIST_HANDLE secondArg = info.compCompHnd->getArgNext(sig->args); |
1150 | CORINFO_CLASS_HANDLE secondArgClass = info.compCompHnd->getArgClass(sig, secondArg); |
1151 | baseType = getBaseTypeOfSIMDType(secondArgClass); |
1152 | retNode = gtNewSimdHWIntrinsicNode(TYP_VOID, op1, vectorOp, lastOp, intrinsic, baseType, 32); |
1153 | } |
1154 | break; |
1155 | } |
1156 | |
1157 | case NI_AVX2_PermuteVar8x32: |
1158 | { |
1159 | baseType = getBaseTypeOfSIMDType(sig->retTypeSigClass); |
1160 | // swap the two operands |
1161 | GenTree* indexVector = impSIMDPopStack(TYP_SIMD32); |
1162 | GenTree* sourceVector = impSIMDPopStack(TYP_SIMD32); |
1163 | retNode = |
1164 | gtNewSimdHWIntrinsicNode(TYP_SIMD32, indexVector, sourceVector, NI_AVX2_PermuteVar8x32, baseType, 32); |
1165 | break; |
1166 | } |
1167 | |
1168 | case NI_AVX2_GatherMaskVector128: |
1169 | case NI_AVX2_GatherMaskVector256: |
1170 | { |
1171 | CORINFO_ARG_LIST_HANDLE argList = sig->args; |
1172 | CORINFO_CLASS_HANDLE argClass; |
1173 | var_types argType = TYP_UNKNOWN; |
1174 | unsigned int sizeBytes; |
1175 | baseType = getBaseTypeAndSizeOfSIMDType(sig->retTypeSigClass, &sizeBytes); |
1176 | var_types retType = getSIMDTypeForSize(sizeBytes); |
1177 | |
1178 | assert(sig->numArgs == 5); |
1179 | CORINFO_ARG_LIST_HANDLE arg2 = info.compCompHnd->getArgNext(argList); |
1180 | CORINFO_ARG_LIST_HANDLE arg3 = info.compCompHnd->getArgNext(arg2); |
1181 | CORINFO_ARG_LIST_HANDLE arg4 = info.compCompHnd->getArgNext(arg3); |
1182 | CORINFO_ARG_LIST_HANDLE arg5 = info.compCompHnd->getArgNext(arg4); |
1183 | |
1184 | argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg5, &argClass))); |
1185 | GenTree* op5 = getArgForHWIntrinsic(argType, argClass); |
1186 | SetOpLclRelatedToSIMDIntrinsic(op5); |
1187 | |
1188 | argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg4, &argClass))); |
1189 | GenTree* op4 = getArgForHWIntrinsic(argType, argClass); |
1190 | SetOpLclRelatedToSIMDIntrinsic(op4); |
1191 | |
1192 | argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg3, &argClass))); |
1193 | var_types indexbaseType = getBaseTypeOfSIMDType(argClass); |
1194 | GenTree* op3 = getArgForHWIntrinsic(argType, argClass); |
1195 | SetOpLclRelatedToSIMDIntrinsic(op3); |
1196 | |
1197 | argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg2, &argClass))); |
1198 | op2 = getArgForHWIntrinsic(argType, argClass); |
1199 | SetOpLclRelatedToSIMDIntrinsic(op2); |
1200 | |
1201 | argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); |
1202 | op1 = getArgForHWIntrinsic(argType, argClass); |
1203 | SetOpLclRelatedToSIMDIntrinsic(op1); |
1204 | |
1205 | GenTree* opList = new (this, GT_LIST) GenTreeArgList(op1, gtNewArgList(op2, op3, op4, op5)); |
1206 | retNode = new (this, GT_HWIntrinsic) GenTreeHWIntrinsic(retType, opList, intrinsic, baseType, simdSize); |
1207 | retNode->AsHWIntrinsic()->gtIndexBaseType = indexbaseType; |
1208 | break; |
1209 | } |
1210 | |
1211 | default: |
1212 | JITDUMP("Not implemented hardware intrinsic" ); |
1213 | break; |
1214 | } |
1215 | return retNode; |
1216 | } |
1217 | |
1218 | GenTree* Compiler::impAESIntrinsic(NamedIntrinsic intrinsic, |
1219 | CORINFO_METHOD_HANDLE method, |
1220 | CORINFO_SIG_INFO* sig, |
1221 | bool mustExpand) |
1222 | { |
1223 | return nullptr; |
1224 | } |
1225 | |
1226 | GenTree* Compiler::impBMI1OrBMI2Intrinsic(NamedIntrinsic intrinsic, |
1227 | CORINFO_METHOD_HANDLE method, |
1228 | CORINFO_SIG_INFO* sig, |
1229 | bool mustExpand) |
1230 | { |
1231 | var_types callType = JITtype2varType(sig->retType); |
1232 | |
1233 | switch (intrinsic) |
1234 | { |
1235 | case NI_BMI1_AndNot: |
1236 | case NI_BMI1_X64_AndNot: |
1237 | case NI_BMI2_ParallelBitDeposit: |
1238 | case NI_BMI2_ParallelBitExtract: |
1239 | case NI_BMI2_X64_ParallelBitDeposit: |
1240 | case NI_BMI2_X64_ParallelBitExtract: |
1241 | { |
1242 | assert(sig->numArgs == 2); |
1243 | |
1244 | GenTree* op2 = impPopStack().val; |
1245 | GenTree* op1 = impPopStack().val; |
1246 | |
1247 | return gtNewScalarHWIntrinsicNode(callType, op1, op2, intrinsic); |
1248 | } |
1249 | |
1250 | case NI_BMI2_ZeroHighBits: |
1251 | case NI_BMI2_X64_ZeroHighBits: |
1252 | { |
1253 | assert(sig->numArgs == 2); |
1254 | |
1255 | GenTree* op2 = impPopStack().val; |
1256 | GenTree* op1 = impPopStack().val; |
1257 | // Instruction BZHI requires to encode op2 (3rd register) in VEX.vvvv and op1 maybe memory operand, |
1258 | // so swap op1 and op2 to unify the backend code. |
1259 | return gtNewScalarHWIntrinsicNode(callType, op2, op1, intrinsic); |
1260 | } |
1261 | |
1262 | case NI_BMI1_ExtractLowestSetBit: |
1263 | case NI_BMI1_GetMaskUpToLowestSetBit: |
1264 | case NI_BMI1_ResetLowestSetBit: |
1265 | case NI_BMI1_TrailingZeroCount: |
1266 | case NI_BMI1_X64_ExtractLowestSetBit: |
1267 | case NI_BMI1_X64_GetMaskUpToLowestSetBit: |
1268 | case NI_BMI1_X64_ResetLowestSetBit: |
1269 | case NI_BMI1_X64_TrailingZeroCount: |
1270 | { |
1271 | assert(sig->numArgs == 1); |
1272 | GenTree* op1 = impPopStack().val; |
1273 | return gtNewScalarHWIntrinsicNode(callType, op1, intrinsic); |
1274 | } |
1275 | |
1276 | case NI_BMI1_BitFieldExtract: |
1277 | case NI_BMI1_X64_BitFieldExtract: |
1278 | { |
1279 | // The 3-arg version is implemented in managed code |
1280 | if (sig->numArgs == 3) |
1281 | { |
1282 | return nullptr; |
1283 | } |
1284 | assert(sig->numArgs == 2); |
1285 | |
1286 | GenTree* op2 = impPopStack().val; |
1287 | GenTree* op1 = impPopStack().val; |
1288 | // Instruction BEXTR requires to encode op2 (3rd register) in VEX.vvvv and op1 maybe memory operand, |
1289 | // so swap op1 and op2 to unify the backend code. |
1290 | return gtNewScalarHWIntrinsicNode(callType, op2, op1, intrinsic); |
1291 | } |
1292 | |
1293 | case NI_BMI2_MultiplyNoFlags: |
1294 | case NI_BMI2_X64_MultiplyNoFlags: |
1295 | { |
1296 | assert(sig->numArgs == 2 || sig->numArgs == 3); |
1297 | GenTree* op3 = nullptr; |
1298 | if (sig->numArgs == 3) |
1299 | { |
1300 | op3 = impPopStack().val; |
1301 | } |
1302 | |
1303 | GenTree* op2 = impPopStack().val; |
1304 | GenTree* op1 = impPopStack().val; |
1305 | |
1306 | if (sig->numArgs == 3) |
1307 | { |
1308 | return gtNewScalarHWIntrinsicNode(callType, op1, op2, op3, intrinsic); |
1309 | } |
1310 | else |
1311 | { |
1312 | return gtNewScalarHWIntrinsicNode(callType, op1, op2, intrinsic); |
1313 | } |
1314 | } |
1315 | |
1316 | default: |
1317 | { |
1318 | unreached(); |
1319 | return nullptr; |
1320 | } |
1321 | } |
1322 | } |
1323 | |
1324 | GenTree* Compiler::impFMAIntrinsic(NamedIntrinsic intrinsic, |
1325 | CORINFO_METHOD_HANDLE method, |
1326 | CORINFO_SIG_INFO* sig, |
1327 | bool mustExpand) |
1328 | { |
1329 | return nullptr; |
1330 | } |
1331 | |
1332 | GenTree* Compiler::impLZCNTIntrinsic(NamedIntrinsic intrinsic, |
1333 | CORINFO_METHOD_HANDLE method, |
1334 | CORINFO_SIG_INFO* sig, |
1335 | bool mustExpand) |
1336 | { |
1337 | assert(sig->numArgs == 1); |
1338 | var_types callType = JITtype2varType(sig->retType); |
1339 | return gtNewScalarHWIntrinsicNode(callType, impPopStack().val, intrinsic); |
1340 | } |
1341 | |
1342 | GenTree* Compiler::impPCLMULQDQIntrinsic(NamedIntrinsic intrinsic, |
1343 | CORINFO_METHOD_HANDLE method, |
1344 | CORINFO_SIG_INFO* sig, |
1345 | bool mustExpand) |
1346 | { |
1347 | return nullptr; |
1348 | } |
1349 | |
1350 | GenTree* Compiler::impPOPCNTIntrinsic(NamedIntrinsic intrinsic, |
1351 | CORINFO_METHOD_HANDLE method, |
1352 | CORINFO_SIG_INFO* sig, |
1353 | bool mustExpand) |
1354 | { |
1355 | assert(sig->numArgs == 1); |
1356 | var_types callType = JITtype2varType(sig->retType); |
1357 | return gtNewScalarHWIntrinsicNode(callType, impPopStack().val, intrinsic); |
1358 | } |
1359 | |
1360 | #endif // FEATURE_HW_INTRINSICS |
1361 | |