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/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
6XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
7XX XX
8XX Lowering for ARM and ARM64 common code XX
9XX XX
10XX This encapsulates common logic for lowering trees for the ARM and ARM64 XX
11XX architectures. For a more detailed view of what is lowering, please XX
12XX take a look at Lower.cpp XX
13XX XX
14XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
15XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
16*/
17
18#include "jitpch.h"
19#ifdef _MSC_VER
20#pragma hdrstop
21#endif
22
23#ifdef _TARGET_ARMARCH_ // This file is ONLY used for ARM and ARM64 architectures
24
25#include "jit.h"
26#include "sideeffects.h"
27#include "lower.h"
28#include "lsra.h"
29
30#ifdef FEATURE_HW_INTRINSICS
31#include "hwintrinsic.h"
32#endif
33
34//------------------------------------------------------------------------
35// IsCallTargetInRange: Can a call target address be encoded in-place?
36//
37// Return Value:
38// True if the addr fits into the range.
39//
40bool Lowering::IsCallTargetInRange(void* addr)
41{
42 return comp->codeGen->validImmForBL((ssize_t)addr);
43}
44
45//------------------------------------------------------------------------
46// IsContainableImmed: Is an immediate encodable in-place?
47//
48// Return Value:
49// True if the immediate can be folded into an instruction,
50// for example small enough and non-relocatable.
51//
52// TODO-CQ: we can contain a floating point 0.0 constant in a compare instruction
53// (vcmp on arm, fcmp on arm64).
54//
55bool Lowering::IsContainableImmed(GenTree* parentNode, GenTree* childNode)
56{
57 if (!varTypeIsFloating(parentNode->TypeGet()))
58 {
59 // Make sure we have an actual immediate
60 if (!childNode->IsCnsIntOrI())
61 return false;
62 if (childNode->gtIntCon.ImmedValNeedsReloc(comp))
63 return false;
64
65 // TODO-CrossBitness: we wouldn't need the cast below if GenTreeIntCon::gtIconVal had target_ssize_t type.
66 target_ssize_t immVal = (target_ssize_t)childNode->gtIntCon.gtIconVal;
67 emitAttr attr = emitActualTypeSize(childNode->TypeGet());
68 emitAttr size = EA_SIZE(attr);
69#ifdef _TARGET_ARM_
70 insFlags flags = parentNode->gtSetFlags() ? INS_FLAGS_SET : INS_FLAGS_DONT_CARE;
71#endif
72
73 switch (parentNode->OperGet())
74 {
75 case GT_ADD:
76 case GT_SUB:
77#ifdef _TARGET_ARM64_
78 case GT_CMPXCHG:
79 case GT_LOCKADD:
80 case GT_XADD:
81 return comp->compSupports(InstructionSet_Atomics) ? false
82 : emitter::emitIns_valid_imm_for_add(immVal, size);
83#elif defined(_TARGET_ARM_)
84 return emitter::emitIns_valid_imm_for_add(immVal, flags);
85#endif
86 break;
87
88#ifdef _TARGET_ARM64_
89 case GT_EQ:
90 case GT_NE:
91 case GT_LT:
92 case GT_LE:
93 case GT_GE:
94 case GT_GT:
95 return emitter::emitIns_valid_imm_for_cmp(immVal, size);
96 case GT_AND:
97 case GT_OR:
98 case GT_XOR:
99 case GT_TEST_EQ:
100 case GT_TEST_NE:
101 return emitter::emitIns_valid_imm_for_alu(immVal, size);
102 case GT_JCMP:
103 assert(((parentNode->gtFlags & GTF_JCMP_TST) == 0) ? (immVal == 0) : isPow2(immVal));
104 return true;
105#elif defined(_TARGET_ARM_)
106 case GT_EQ:
107 case GT_NE:
108 case GT_LT:
109 case GT_LE:
110 case GT_GE:
111 case GT_GT:
112 case GT_CMP:
113 case GT_AND:
114 case GT_OR:
115 case GT_XOR:
116 return emitter::emitIns_valid_imm_for_alu(immVal);
117#endif // _TARGET_ARM_
118
119#ifdef _TARGET_ARM64_
120 case GT_STORE_LCL_FLD:
121 case GT_STORE_LCL_VAR:
122 if (immVal == 0)
123 return true;
124 break;
125#endif
126
127 default:
128 break;
129 }
130 }
131
132 return false;
133}
134
135//------------------------------------------------------------------------
136// LowerStoreLoc: Lower a store of a lclVar
137//
138// Arguments:
139// storeLoc - the local store (GT_STORE_LCL_FLD or GT_STORE_LCL_VAR)
140//
141// Notes:
142// This involves:
143// - Widening operations of unsigneds.
144//
145void Lowering::LowerStoreLoc(GenTreeLclVarCommon* storeLoc)
146{
147 // Try to widen the ops if they are going into a local var.
148 GenTree* op1 = storeLoc->gtGetOp1();
149 if ((storeLoc->gtOper == GT_STORE_LCL_VAR) && (op1->gtOper == GT_CNS_INT))
150 {
151 GenTreeIntCon* con = op1->AsIntCon();
152 ssize_t ival = con->gtIconVal;
153 unsigned varNum = storeLoc->gtLclNum;
154 LclVarDsc* varDsc = comp->lvaTable + varNum;
155
156 if (varDsc->lvIsSIMDType())
157 {
158 noway_assert(storeLoc->gtType != TYP_STRUCT);
159 }
160 unsigned size = genTypeSize(storeLoc);
161 // If we are storing a constant into a local variable
162 // we extend the size of the store here
163 if ((size < 4) && !varTypeIsStruct(varDsc))
164 {
165 if (!varTypeIsUnsigned(varDsc))
166 {
167 if (genTypeSize(storeLoc) == 1)
168 {
169 if ((ival & 0x7f) != ival)
170 {
171 ival = ival | 0xffffff00;
172 }
173 }
174 else
175 {
176 assert(genTypeSize(storeLoc) == 2);
177 if ((ival & 0x7fff) != ival)
178 {
179 ival = ival | 0xffff0000;
180 }
181 }
182 }
183
184 // A local stack slot is at least 4 bytes in size, regardless of
185 // what the local var is typed as, so auto-promote it here
186 // unless it is a field of a promoted struct
187 // TODO-CQ: if the field is promoted shouldn't we also be able to do this?
188 if (!varDsc->lvIsStructField)
189 {
190 storeLoc->gtType = TYP_INT;
191 con->SetIconValue(ival);
192 }
193 }
194 }
195 if (storeLoc->OperIs(GT_STORE_LCL_FLD))
196 {
197 // We should only encounter this for lclVars that are lvDoNotEnregister.
198 verifyLclFldDoNotEnregister(storeLoc->gtLclNum);
199 }
200 ContainCheckStoreLoc(storeLoc);
201}
202
203//------------------------------------------------------------------------
204// LowerStoreIndir: Determine addressing mode for an indirection, and whether operands are contained.
205//
206// Arguments:
207// node - The indirect store node (GT_STORE_IND) of interest
208//
209// Return Value:
210// None.
211//
212void Lowering::LowerStoreIndir(GenTreeIndir* node)
213{
214 ContainCheckStoreIndir(node);
215}
216
217//------------------------------------------------------------------------
218// LowerBlockStore: Set block store type
219//
220// Arguments:
221// blkNode - The block store node of interest
222//
223// Return Value:
224// None.
225//
226void Lowering::LowerBlockStore(GenTreeBlk* blkNode)
227{
228 GenTree* dstAddr = blkNode->Addr();
229 unsigned size = blkNode->gtBlkSize;
230 GenTree* source = blkNode->Data();
231 Compiler* compiler = comp;
232
233 // Sources are dest address and initVal or source.
234 GenTree* srcAddrOrFill = nullptr;
235 bool isInitBlk = blkNode->OperIsInitBlkOp();
236
237 if (!isInitBlk)
238 {
239 // CopyObj or CopyBlk
240 if ((blkNode->OperGet() == GT_STORE_OBJ) && ((blkNode->AsObj()->gtGcPtrCount == 0) || blkNode->gtBlkOpGcUnsafe))
241 {
242 blkNode->SetOper(GT_STORE_BLK);
243 }
244 if (source->gtOper == GT_IND)
245 {
246 srcAddrOrFill = blkNode->Data()->gtGetOp1();
247 }
248 }
249
250 if (isInitBlk)
251 {
252 GenTree* initVal = source;
253 if (initVal->OperIsInitVal())
254 {
255 initVal->SetContained();
256 initVal = initVal->gtGetOp1();
257 }
258 srcAddrOrFill = initVal;
259
260#ifdef _TARGET_ARM64_
261 if ((size != 0) && (size <= INITBLK_UNROLL_LIMIT) && initVal->IsCnsIntOrI())
262 {
263 // TODO-ARM-CQ: Currently we generate a helper call for every
264 // initblk we encounter. Later on we should implement loop unrolling
265 // code sequences to improve CQ.
266 // For reference see the code in LowerXArch.cpp.
267 NYI_ARM("initblk loop unrolling is currently not implemented.");
268
269 // The fill value of an initblk is interpreted to hold a
270 // value of (unsigned int8) however a constant of any size
271 // may practically reside on the evaluation stack. So extract
272 // the lower byte out of the initVal constant and replicate
273 // it to a larger constant whose size is sufficient to support
274 // the largest width store of the desired inline expansion.
275
276 ssize_t fill = initVal->gtIntCon.gtIconVal & 0xFF;
277 if (fill == 0)
278 {
279 MakeSrcContained(blkNode, source);
280 }
281 else if (size < REGSIZE_BYTES)
282 {
283 initVal->gtIntCon.gtIconVal = 0x01010101 * fill;
284 }
285 else
286 {
287 initVal->gtIntCon.gtIconVal = 0x0101010101010101LL * fill;
288 initVal->gtType = TYP_LONG;
289 }
290 blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindUnroll;
291 }
292 else
293#endif // _TARGET_ARM64_
294 {
295 blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindHelper;
296 }
297 }
298 else
299 {
300 // CopyObj or CopyBlk
301 // Sources are src and dest and size if not constant.
302
303 if (blkNode->OperGet() == GT_STORE_OBJ)
304 {
305 // CopyObj
306 GenTreeObj* objNode = blkNode->AsObj();
307
308 unsigned slots = objNode->gtSlots;
309
310#ifdef DEBUG
311 // CpObj must always have at least one GC-Pointer as a member.
312 assert(objNode->gtGcPtrCount > 0);
313
314 assert(dstAddr->gtType == TYP_BYREF || dstAddr->gtType == TYP_I_IMPL);
315
316 CORINFO_CLASS_HANDLE clsHnd = objNode->gtClass;
317 size_t classSize = compiler->info.compCompHnd->getClassSize(clsHnd);
318 size_t blkSize = roundUp(classSize, TARGET_POINTER_SIZE);
319
320 // Currently, the EE always round up a class data structure so
321 // we are not handling the case where we have a non multiple of pointer sized
322 // struct. This behavior may change in the future so in order to keeps things correct
323 // let's assert it just to be safe. Going forward we should simply
324 // handle this case.
325 assert(classSize == blkSize);
326 assert((blkSize / TARGET_POINTER_SIZE) == slots);
327 assert(objNode->HasGCPtr());
328#endif
329
330 blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindUnroll;
331 }
332 else // CopyBlk
333 {
334 // In case of a CpBlk with a constant size and less than CPBLK_UNROLL_LIMIT size
335 // we should unroll the loop to improve CQ.
336 // For reference see the code in lowerxarch.cpp.
337
338 if ((size != 0) && (size <= CPBLK_UNROLL_LIMIT))
339 {
340 blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindUnroll;
341 }
342 else
343 {
344 // In case we have a constant integer this means we went beyond
345 // CPBLK_UNROLL_LIMIT bytes of size, still we should never have the case of
346 // any GC-Pointers in the src struct.
347 blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindHelper;
348 }
349 }
350 // CopyObj or CopyBlk
351 if (source->gtOper == GT_IND)
352 {
353 MakeSrcContained(blkNode, source);
354 GenTree* addr = source->AsIndir()->Addr();
355 if (!addr->OperIsLocalAddr())
356 {
357 addr->ClearContained();
358 }
359 }
360 else if (!source->IsMultiRegCall() && !source->OperIsSIMDorSimdHWintrinsic())
361 {
362 assert(source->IsLocal());
363 MakeSrcContained(blkNode, source);
364 }
365 }
366}
367
368//------------------------------------------------------------------------
369// LowerCast: Lower GT_CAST(srcType, DstType) nodes.
370//
371// Arguments:
372// tree - GT_CAST node to be lowered
373//
374// Return Value:
375// None.
376//
377// Notes:
378// Casts from float/double to a smaller int type are transformed as follows:
379// GT_CAST(float/double, byte) = GT_CAST(GT_CAST(float/double, int32), byte)
380// GT_CAST(float/double, sbyte) = GT_CAST(GT_CAST(float/double, int32), sbyte)
381// GT_CAST(float/double, int16) = GT_CAST(GT_CAST(double/double, int32), int16)
382// GT_CAST(float/double, uint16) = GT_CAST(GT_CAST(double/double, int32), uint16)
383//
384// Note that for the overflow conversions we still depend on helper calls and
385// don't expect to see them here.
386// i) GT_CAST(float/double, int type with overflow detection)
387//
388void Lowering::LowerCast(GenTree* tree)
389{
390 assert(tree->OperGet() == GT_CAST);
391
392 JITDUMP("LowerCast for: ");
393 DISPNODE(tree);
394 JITDUMP("\n");
395
396 GenTree* op1 = tree->gtOp.gtOp1;
397 var_types dstType = tree->CastToType();
398 var_types srcType = genActualType(op1->TypeGet());
399 var_types tmpType = TYP_UNDEF;
400
401 if (varTypeIsFloating(srcType))
402 {
403 noway_assert(!tree->gtOverflow());
404 assert(!varTypeIsSmall(dstType)); // fgMorphCast creates intermediate casts when converting from float to small
405 // int.
406 }
407
408 assert(!varTypeIsSmall(srcType));
409
410 if (tmpType != TYP_UNDEF)
411 {
412 GenTree* tmp = comp->gtNewCastNode(tmpType, op1, tree->IsUnsigned(), tmpType);
413 tmp->gtFlags |= (tree->gtFlags & (GTF_OVERFLOW | GTF_EXCEPT));
414
415 tree->gtFlags &= ~GTF_UNSIGNED;
416 tree->gtOp.gtOp1 = tmp;
417 BlockRange().InsertAfter(op1, tmp);
418 }
419
420 // Now determine if we have operands that should be contained.
421 ContainCheckCast(tree->AsCast());
422}
423
424//------------------------------------------------------------------------
425// LowerRotate: Lower GT_ROL and GT_ROR nodes.
426//
427// Arguments:
428// tree - the node to lower
429//
430// Return Value:
431// None.
432//
433void Lowering::LowerRotate(GenTree* tree)
434{
435 if (tree->OperGet() == GT_ROL)
436 {
437 // There is no ROL instruction on ARM. Convert ROL into ROR.
438 GenTree* rotatedValue = tree->gtOp.gtOp1;
439 unsigned rotatedValueBitSize = genTypeSize(rotatedValue->gtType) * 8;
440 GenTree* rotateLeftIndexNode = tree->gtOp.gtOp2;
441
442 if (rotateLeftIndexNode->IsCnsIntOrI())
443 {
444 ssize_t rotateLeftIndex = rotateLeftIndexNode->gtIntCon.gtIconVal;
445 ssize_t rotateRightIndex = rotatedValueBitSize - rotateLeftIndex;
446 rotateLeftIndexNode->gtIntCon.gtIconVal = rotateRightIndex;
447 }
448 else
449 {
450 GenTree* tmp = comp->gtNewOperNode(GT_NEG, genActualType(rotateLeftIndexNode->gtType), rotateLeftIndexNode);
451 BlockRange().InsertAfter(rotateLeftIndexNode, tmp);
452 tree->gtOp.gtOp2 = tmp;
453 }
454 tree->ChangeOper(GT_ROR);
455 }
456 ContainCheckShiftRotate(tree->AsOp());
457}
458
459#ifdef FEATURE_SIMD
460//----------------------------------------------------------------------------------------------
461// Lowering::LowerSIMD: Perform containment analysis for a SIMD intrinsic node.
462//
463// Arguments:
464// simdNode - The SIMD intrinsic node.
465//
466void Lowering::LowerSIMD(GenTreeSIMD* simdNode)
467{
468 assert(simdNode->gtType != TYP_SIMD32);
469
470 if (simdNode->TypeGet() == TYP_SIMD12)
471 {
472 // GT_SIMD node requiring to produce TYP_SIMD12 in fact
473 // produces a TYP_SIMD16 result
474 simdNode->gtType = TYP_SIMD16;
475 }
476
477 ContainCheckSIMD(simdNode);
478}
479#endif // FEATURE_SIMD
480
481#ifdef FEATURE_HW_INTRINSICS
482//----------------------------------------------------------------------------------------------
483// Lowering::LowerHWIntrinsic: Perform containment analysis for a hardware intrinsic node.
484//
485// Arguments:
486// node - The hardware intrinsic node.
487//
488void Lowering::LowerHWIntrinsic(GenTreeHWIntrinsic* node)
489{
490 auto intrinsicID = node->gtHWIntrinsicId;
491 auto intrinsicInfo = HWIntrinsicInfo::lookup(node->gtHWIntrinsicId);
492
493 //
494 // Lower unsupported Unsigned Compare Zero intrinsics to their trivial transformations
495 //
496 // ARM64 does not support most forms of compare zero for Unsigned values
497 // This is because some are non-sensical, and the rest are trivial transformations of other operators
498 //
499 if ((intrinsicInfo.flags & HWIntrinsicInfo::LowerCmpUZero) && varTypeIsUnsigned(node->gtSIMDBaseType))
500 {
501 auto setAllVector = node->gtSIMDSize > 8 ? NI_ARM64_SIMD_SetAllVector128 : NI_ARM64_SIMD_SetAllVector64;
502
503 auto origOp1 = node->gtOp.gtOp1;
504
505 switch (intrinsicID)
506 {
507 case NI_ARM64_SIMD_GT_ZERO:
508 // Unsigned > 0 ==> !(Unsigned == 0)
509 node->gtOp.gtOp1 =
510 comp->gtNewSimdHWIntrinsicNode(node->TypeGet(), node->gtOp.gtOp1, NI_ARM64_SIMD_EQ_ZERO,
511 node->gtSIMDBaseType, node->gtSIMDSize);
512 node->gtHWIntrinsicId = NI_ARM64_SIMD_BitwiseNot;
513 BlockRange().InsertBefore(node, node->gtOp.gtOp1);
514 break;
515 case NI_ARM64_SIMD_LE_ZERO:
516 // Unsigned <= 0 ==> Unsigned == 0
517 node->gtHWIntrinsicId = NI_ARM64_SIMD_EQ_ZERO;
518 break;
519 case NI_ARM64_SIMD_GE_ZERO:
520 case NI_ARM64_SIMD_LT_ZERO:
521 // Unsigned >= 0 ==> Always true
522 // Unsigned < 0 ==> Always false
523 node->gtHWIntrinsicId = setAllVector;
524 node->gtOp.gtOp1 = comp->gtNewLconNode((intrinsicID == NI_ARM64_SIMD_GE_ZERO) ? ~0ULL : 0ULL);
525 BlockRange().InsertBefore(node, node->gtOp.gtOp1);
526 if ((origOp1->gtFlags & GTF_ALL_EFFECT) == 0)
527 {
528 BlockRange().Remove(origOp1, true);
529 }
530 else
531 {
532 origOp1->SetUnusedValue();
533 }
534 break;
535 default:
536 assert(!"Unhandled LowerCmpUZero case");
537 }
538 }
539
540 ContainCheckHWIntrinsic(node);
541}
542#endif // FEATURE_HW_INTRINSICS
543
544//------------------------------------------------------------------------
545// Containment analysis
546//------------------------------------------------------------------------
547
548//------------------------------------------------------------------------
549// ContainCheckCallOperands: Determine whether operands of a call should be contained.
550//
551// Arguments:
552// call - The call node of interest
553//
554// Return Value:
555// None.
556//
557void Lowering::ContainCheckCallOperands(GenTreeCall* call)
558{
559 // There are no contained operands for arm.
560}
561
562//------------------------------------------------------------------------
563// ContainCheckStoreIndir: determine whether the sources of a STOREIND node should be contained.
564//
565// Arguments:
566// node - pointer to the node
567//
568void Lowering::ContainCheckStoreIndir(GenTreeIndir* node)
569{
570#ifdef _TARGET_ARM64_
571 GenTree* src = node->gtOp.gtOp2;
572 if (!varTypeIsFloating(src->TypeGet()) && src->IsIntegralConst(0))
573 {
574 // an integer zero for 'src' can be contained.
575 MakeSrcContained(node, src);
576 }
577#endif // _TARGET_ARM64_
578 ContainCheckIndir(node);
579}
580
581//------------------------------------------------------------------------
582// ContainCheckIndir: Determine whether operands of an indir should be contained.
583//
584// Arguments:
585// indirNode - The indirection node of interest
586//
587// Notes:
588// This is called for both store and load indirections.
589//
590// Return Value:
591// None.
592//
593void Lowering::ContainCheckIndir(GenTreeIndir* indirNode)
594{
595 // If this is the rhs of a block copy it will be handled when we handle the store.
596 if (indirNode->TypeGet() == TYP_STRUCT)
597 {
598 return;
599 }
600
601#ifdef FEATURE_SIMD
602 // If indirTree is of TYP_SIMD12, don't mark addr as contained
603 // so that it always get computed to a register. This would
604 // mean codegen side logic doesn't need to handle all possible
605 // addr expressions that could be contained.
606 //
607 // TODO-ARM64-CQ: handle other addr mode expressions that could be marked
608 // as contained.
609 if (indirNode->TypeGet() == TYP_SIMD12)
610 {
611 return;
612 }
613#endif // FEATURE_SIMD
614
615 GenTree* addr = indirNode->Addr();
616 bool makeContained = true;
617 if ((addr->OperGet() == GT_LEA) && IsSafeToContainMem(indirNode, addr))
618 {
619 GenTreeAddrMode* lea = addr->AsAddrMode();
620 GenTree* base = lea->Base();
621 GenTree* index = lea->Index();
622 int cns = lea->Offset();
623
624#ifdef _TARGET_ARM_
625 // ARM floating-point load/store doesn't support a form similar to integer
626 // ldr Rdst, [Rbase + Roffset] with offset in a register. The only supported
627 // form is vldr Rdst, [Rbase + imm] with a more limited constraint on the imm.
628 if (lea->HasIndex() || !emitter::emitIns_valid_imm_for_vldst_offset(cns))
629 {
630 if (indirNode->OperGet() == GT_STOREIND)
631 {
632 if (varTypeIsFloating(indirNode->AsStoreInd()->Data()))
633 {
634 makeContained = false;
635 }
636 }
637 else if (indirNode->OperGet() == GT_IND)
638 {
639 if (varTypeIsFloating(indirNode))
640 {
641 makeContained = false;
642 }
643 }
644 }
645#endif
646 if (makeContained)
647 {
648 MakeSrcContained(indirNode, addr);
649 }
650 }
651}
652
653//------------------------------------------------------------------------
654// ContainCheckBinary: Determine whether a binary op's operands should be contained.
655//
656// Arguments:
657// node - the node we care about
658//
659void Lowering::ContainCheckBinary(GenTreeOp* node)
660{
661 // Check and make op2 contained (if it is a containable immediate)
662 CheckImmedAndMakeContained(node, node->gtOp2);
663}
664
665//------------------------------------------------------------------------
666// ContainCheckMul: Determine whether a mul op's operands should be contained.
667//
668// Arguments:
669// node - the node we care about
670//
671void Lowering::ContainCheckMul(GenTreeOp* node)
672{
673 ContainCheckBinary(node);
674}
675
676//------------------------------------------------------------------------
677// ContainCheckDivOrMod: determine which operands of a div/mod should be contained.
678//
679// Arguments:
680// node - the node we care about
681//
682void Lowering::ContainCheckDivOrMod(GenTreeOp* node)
683{
684 assert(node->OperIs(GT_DIV, GT_UDIV));
685
686 // ARM doesn't have a div instruction with an immediate operand
687}
688
689//------------------------------------------------------------------------
690// ContainCheckShiftRotate: Determine whether a mul op's operands should be contained.
691//
692// Arguments:
693// node - the node we care about
694//
695void Lowering::ContainCheckShiftRotate(GenTreeOp* node)
696{
697 GenTree* shiftBy = node->gtOp2;
698 assert(node->OperIsShiftOrRotate());
699
700#ifdef _TARGET_ARM_
701 GenTree* source = node->gtOp1;
702 if (node->OperIs(GT_LSH_HI, GT_RSH_LO))
703 {
704 assert(source->OperGet() == GT_LONG);
705 MakeSrcContained(node, source);
706 }
707#endif // _TARGET_ARM_
708
709 if (shiftBy->IsCnsIntOrI())
710 {
711 MakeSrcContained(node, shiftBy);
712 }
713}
714
715//------------------------------------------------------------------------
716// ContainCheckStoreLoc: determine whether the source of a STORE_LCL* should be contained.
717//
718// Arguments:
719// node - pointer to the node
720//
721void Lowering::ContainCheckStoreLoc(GenTreeLclVarCommon* storeLoc)
722{
723 assert(storeLoc->OperIsLocalStore());
724 GenTree* op1 = storeLoc->gtGetOp1();
725
726#ifdef FEATURE_SIMD
727 if (varTypeIsSIMD(storeLoc))
728 {
729 if (op1->IsIntegralConst(0))
730 {
731 // For an InitBlk we want op1 to be contained
732 MakeSrcContained(storeLoc, op1);
733 }
734 return;
735 }
736#endif // FEATURE_SIMD
737
738 // If the source is a containable immediate, make it contained, unless it is
739 // an int-size or larger store of zero to memory, because we can generate smaller code
740 // by zeroing a register and then storing it.
741 if (IsContainableImmed(storeLoc, op1) && (!op1->IsIntegralConst(0) || varTypeIsSmall(storeLoc)))
742 {
743 MakeSrcContained(storeLoc, op1);
744 }
745#ifdef _TARGET_ARM_
746 else if (op1->OperGet() == GT_LONG)
747 {
748 MakeSrcContained(storeLoc, op1);
749 }
750#endif // _TARGET_ARM_
751}
752
753//------------------------------------------------------------------------
754// ContainCheckCast: determine whether the source of a CAST node should be contained.
755//
756// Arguments:
757// node - pointer to the node
758//
759void Lowering::ContainCheckCast(GenTreeCast* node)
760{
761#ifdef _TARGET_ARM_
762 GenTree* castOp = node->CastOp();
763 var_types castToType = node->CastToType();
764 var_types srcType = castOp->TypeGet();
765
766 if (varTypeIsLong(castOp))
767 {
768 assert(castOp->OperGet() == GT_LONG);
769 MakeSrcContained(node, castOp);
770 }
771#endif // _TARGET_ARM_
772}
773
774//------------------------------------------------------------------------
775// ContainCheckCompare: determine whether the sources of a compare node should be contained.
776//
777// Arguments:
778// node - pointer to the node
779//
780void Lowering::ContainCheckCompare(GenTreeOp* cmp)
781{
782 CheckImmedAndMakeContained(cmp, cmp->gtOp2);
783}
784
785//------------------------------------------------------------------------
786// ContainCheckBoundsChk: determine whether any source of a bounds check node should be contained.
787//
788// Arguments:
789// node - pointer to the node
790//
791void Lowering::ContainCheckBoundsChk(GenTreeBoundsChk* node)
792{
793 assert(node->OperIsBoundsCheck());
794 if (!CheckImmedAndMakeContained(node, node->gtIndex))
795 {
796 CheckImmedAndMakeContained(node, node->gtArrLen);
797 }
798}
799
800#ifdef FEATURE_SIMD
801//----------------------------------------------------------------------------------------------
802// ContainCheckSIMD: Perform containment analysis for a SIMD intrinsic node.
803//
804// Arguments:
805// simdNode - The SIMD intrinsic node.
806//
807void Lowering::ContainCheckSIMD(GenTreeSIMD* simdNode)
808{
809 switch (simdNode->gtSIMDIntrinsicID)
810 {
811 GenTree* op1;
812 GenTree* op2;
813
814 case SIMDIntrinsicInit:
815 op1 = simdNode->gtOp.gtOp1;
816 if (op1->IsIntegralConst(0))
817 {
818 MakeSrcContained(simdNode, op1);
819 }
820 break;
821
822 case SIMDIntrinsicInitArray:
823 // We have an array and an index, which may be contained.
824 CheckImmedAndMakeContained(simdNode, simdNode->gtGetOp2());
825 break;
826
827 case SIMDIntrinsicOpEquality:
828 case SIMDIntrinsicOpInEquality:
829 // TODO-ARM64-CQ Support containing 0
830 break;
831
832 case SIMDIntrinsicGetItem:
833 {
834 // This implements get_Item method. The sources are:
835 // - the source SIMD struct
836 // - index (which element to get)
837 // The result is baseType of SIMD struct.
838 op1 = simdNode->gtOp.gtOp1;
839 op2 = simdNode->gtOp.gtOp2;
840
841 // If the index is a constant, mark it as contained.
842 if (op2->IsCnsIntOrI())
843 {
844 MakeSrcContained(simdNode, op2);
845 }
846
847 if (IsContainableMemoryOp(op1))
848 {
849 MakeSrcContained(simdNode, op1);
850 if (op1->OperGet() == GT_IND)
851 {
852 op1->AsIndir()->Addr()->ClearContained();
853 }
854 }
855 break;
856 }
857
858 default:
859 break;
860 }
861}
862#endif // FEATURE_SIMD
863
864#ifdef FEATURE_HW_INTRINSICS
865//----------------------------------------------------------------------------------------------
866// ContainCheckHWIntrinsic: Perform containment analysis for a hardware intrinsic node.
867//
868// Arguments:
869// node - The hardware intrinsic node.
870//
871void Lowering::ContainCheckHWIntrinsic(GenTreeHWIntrinsic* node)
872{
873 NamedIntrinsic intrinsicID = node->gtHWIntrinsicId;
874 GenTreeArgList* argList = nullptr;
875 GenTree* op1 = node->gtOp.gtOp1;
876 GenTree* op2 = node->gtOp.gtOp2;
877
878 if (op1->OperIs(GT_LIST))
879 {
880 argList = op1->AsArgList();
881 op1 = argList->Current();
882 op2 = argList->Rest()->Current();
883 }
884
885 switch (HWIntrinsicInfo::lookup(node->gtHWIntrinsicId).form)
886 {
887 case HWIntrinsicInfo::SimdExtractOp:
888 if (op2->IsCnsIntOrI())
889 {
890 MakeSrcContained(node, op2);
891 }
892 break;
893
894 case HWIntrinsicInfo::SimdInsertOp:
895 if (op2->IsCnsIntOrI())
896 {
897 MakeSrcContained(node, op2);
898
899 GenTree* op3 = argList->Rest()->Rest()->Current();
900
901 // In the HW intrinsics C# API there is no direct way to specify a vector element to element mov
902 // VX[a] = VY[b]
903 // In C# this would naturally be expressed by
904 // Insert(VX, a, Extract(VY, b))
905 // If both a & b are immediate constants contain the extract/getItem so that we can emit
906 // the single instruction mov Vx[a], Vy[b]
907 if (op3->OperIs(GT_HWIntrinsic) && (op3->AsHWIntrinsic()->gtHWIntrinsicId == NI_ARM64_SIMD_GetItem))
908 {
909 ContainCheckHWIntrinsic(op3->AsHWIntrinsic());
910
911 if (op3->gtOp.gtOp2->isContained())
912 {
913 MakeSrcContained(node, op3);
914 }
915 }
916 }
917 break;
918
919 default:
920 break;
921 }
922}
923#endif // FEATURE_HW_INTRINSICS
924
925#endif // _TARGET_ARMARCH_
926