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 |
6 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
7 | XX XX |
8 | XX Lowering for ARM and ARM64 common code XX |
9 | XX XX |
10 | XX This encapsulates common logic for lowering trees for the ARM and ARM64 XX |
11 | XX architectures. For a more detailed view of what is lowering, please XX |
12 | XX take a look at Lower.cpp XX |
13 | XX XX |
14 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
15 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
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 | // |
40 | bool 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 | // |
55 | bool 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 | // |
145 | void 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 | // |
212 | void 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 | // |
226 | void 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 | // |
388 | void 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 | // |
433 | void 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 | // |
466 | void 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 | // |
488 | void 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 | // |
557 | void 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 | // |
568 | void 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 | // |
593 | void 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 | // |
659 | void 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 | // |
671 | void 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 | // |
682 | void 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 | // |
695 | void 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 | // |
721 | void 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 | // |
759 | void 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 | // |
780 | void 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 | // |
791 | void 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 | // |
807 | void 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 | // |
871 | void 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 | |