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// Early Value Propagation
6//
7// This phase performs an SSA-based value propagation optimization that currently only applies to array
8// lengths, runtime type handles, and explicit null checks. An SSA-based backwards tracking of local variables
9// is performed at each point of interest, e.g., an array length reference site, a method table reference site, or
10// an indirection.
11// The tracking continues until an interesting value is encountered. The value is then used to rewrite
12// the source site or the value.
13//
14///////////////////////////////////////////////////////////////////////////////////////
15
16#include "jitpch.h"
17#include "ssabuilder.h"
18
19bool Compiler::optDoEarlyPropForFunc()
20{
21 bool propArrayLen = (optMethodFlags & OMF_HAS_NEWARRAY) && (optMethodFlags & OMF_HAS_ARRAYREF);
22 bool propGetType = (optMethodFlags & OMF_HAS_NEWOBJ) && (optMethodFlags & OMF_HAS_VTABLEREF);
23 bool propNullCheck = (optMethodFlags & OMF_HAS_NULLCHECK) != 0;
24 return propArrayLen || propGetType || propNullCheck;
25}
26
27bool Compiler::optDoEarlyPropForBlock(BasicBlock* block)
28{
29 bool bbHasArrayRef = (block->bbFlags & BBF_HAS_IDX_LEN) != 0;
30 bool bbHasVtableRef = (block->bbFlags & BBF_HAS_VTABREF) != 0;
31 bool bbHasNullCheck = (block->bbFlags & BBF_HAS_NULLCHECK) != 0;
32 return bbHasArrayRef || bbHasVtableRef || bbHasNullCheck;
33}
34
35//--------------------------------------------------------------------
36// gtIsVtableRef: Return true if the tree is a method table reference.
37//
38// Arguments:
39// tree - The input tree.
40//
41// Return Value:
42// Return true if the tree is a method table reference.
43
44bool Compiler::gtIsVtableRef(GenTree* tree)
45{
46 if (tree->OperGet() == GT_IND)
47 {
48 GenTree* addr = tree->AsIndir()->Addr();
49
50 if (addr->OperIsAddrMode())
51 {
52 GenTreeAddrMode* addrMode = addr->AsAddrMode();
53
54 return (!addrMode->HasIndex() && (addrMode->Base()->TypeGet() == TYP_REF));
55 }
56 }
57
58 return false;
59}
60
61//------------------------------------------------------------------------------
62// getArrayLengthFromAllocation: Return the array length for an array allocation
63// helper call.
64//
65// Arguments:
66// tree - The array allocation helper call.
67//
68// Return Value:
69// Return the array length node.
70
71GenTree* Compiler::getArrayLengthFromAllocation(GenTree* tree)
72{
73 assert(tree != nullptr);
74
75 if (tree->OperGet() == GT_CALL)
76 {
77 GenTreeCall* call = tree->AsCall();
78
79 if (call->gtCallType == CT_HELPER)
80 {
81 if (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_DIRECT) ||
82 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_R2R_DIRECT) ||
83 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_OBJ) ||
84 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_VC) ||
85 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_ALIGN8))
86 {
87 // This is an array allocation site. Grab the array length node.
88 return gtArgEntryByArgNum(call, 1)->node;
89 }
90 }
91 }
92
93 return nullptr;
94}
95
96//-----------------------------------------------------------------------------
97// getObjectHandleNodeFromAllocation: Return the type handle for an object allocation
98// helper call.
99//
100// Arguments:
101// tree - The object allocation helper call.
102//
103// Return Value:
104// Return the object type handle node.
105
106GenTree* Compiler::getObjectHandleNodeFromAllocation(GenTree* tree)
107{
108 assert(tree != nullptr);
109
110 if (tree->OperGet() == GT_CALL)
111 {
112 GenTreeCall* call = tree->AsCall();
113
114 if (call->gtCallType == CT_HELPER)
115 {
116 if (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWFAST) ||
117 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWSFAST) ||
118 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWSFAST_FINALIZE) ||
119 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWSFAST_ALIGN8) ||
120 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWSFAST_ALIGN8_VC) ||
121 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWSFAST_ALIGN8_FINALIZE) ||
122 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_DIRECT) ||
123 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_R2R_DIRECT) ||
124 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_OBJ) ||
125 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_VC) ||
126 call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_NEWARR_1_ALIGN8))
127 {
128 // This is an object allocation site. Return the runtime type handle node.
129 fgArgTabEntry* argTabEntry = gtArgEntryByArgNum(call, 0);
130 return argTabEntry->node;
131 }
132 }
133 }
134
135 return nullptr;
136}
137
138//------------------------------------------------------------------------------------------
139// optEarlyProp: The entry point of the early value propagation.
140//
141// Notes:
142// This phase performs an SSA-based value propagation, including
143// 1. Array length propagation.
144// 2. Runtime type handle propagation.
145// 3. Null check folding.
146//
147// For array length propagation, a demand-driven SSA-based backwards tracking of constant
148// array lengths is performed at each array length reference site which is in form of a
149// GT_ARR_LENGTH node. When a GT_ARR_LENGTH node is seen, the array ref pointer which is
150// the only child node of the GT_ARR_LENGTH is tracked. This is only done for array ref
151// pointers that have valid SSA forms.The tracking is along SSA use-def chain and stops
152// at the original array allocation site where we can grab the array length. The
153// GT_ARR_LENGTH node will then be rewritten to a GT_CNS_INT node if the array length is
154// constant.
155//
156// Similarly, the same algorithm also applies to rewriting a method table (also known as
157// vtable) reference site which is in form of GT_INDIR node. The base pointer, which is
158// an object reference pointer, is treated in the same way as an array reference pointer.
159//
160// Null check folding tries to find GT_INDIR(obj + const) that GT_NULLCHECK(obj) can be folded into
161// and removed. Currently, the algorithm only matches GT_INDIR and GT_NULLCHECK in the same basic block.
162
163void Compiler::optEarlyProp()
164{
165#ifdef DEBUG
166 if (verbose)
167 {
168 printf("*************** In optEarlyProp()\n");
169 }
170#endif
171
172 assert(fgSsaPassesCompleted == 1);
173
174 if (!optDoEarlyPropForFunc())
175 {
176 return;
177 }
178
179 for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
180 {
181 if (!optDoEarlyPropForBlock(block))
182 {
183 continue;
184 }
185
186 compCurBB = block;
187
188 for (GenTreeStmt* stmt = block->firstStmt(); stmt != nullptr;)
189 {
190 // Preserve the next link before the propagation and morph.
191 GenTreeStmt* next = stmt->gtNextStmt;
192
193 compCurStmt = stmt;
194
195 // Walk the stmt tree in linear order to rewrite any array length reference with a
196 // constant array length.
197 bool isRewritten = false;
198 for (GenTree* tree = stmt->gtStmt.gtStmtList; tree != nullptr; tree = tree->gtNext)
199 {
200 GenTree* rewrittenTree = optEarlyPropRewriteTree(tree);
201 if (rewrittenTree != nullptr)
202 {
203 gtUpdateSideEffects(stmt, rewrittenTree);
204 isRewritten = true;
205 tree = rewrittenTree;
206 }
207 }
208
209 // Update the evaluation order and the statement info if the stmt has been rewritten.
210 if (isRewritten)
211 {
212 gtSetStmtInfo(stmt);
213 fgSetStmtSeq(stmt);
214 }
215
216 stmt = next;
217 }
218 }
219
220#ifdef DEBUG
221 if (verbose)
222 {
223 JITDUMP("\nAfter optEarlyProp:\n");
224 fgDispBasicBlocks(/*dumpTrees*/ true);
225 }
226#endif
227}
228
229//----------------------------------------------------------------
230// optEarlyPropRewriteValue: Rewrite a tree to the actual value.
231//
232// Arguments:
233// tree - The input tree node to be rewritten.
234//
235// Return Value:
236// Return a new tree if the original tree was successfully rewritten.
237// The containing tree links are updated.
238//
239GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree)
240{
241 GenTree* objectRefPtr = nullptr;
242 optPropKind propKind = optPropKind::OPK_INVALID;
243
244 if (tree->OperGet() == GT_ARR_LENGTH)
245 {
246 objectRefPtr = tree->gtOp.gtOp1;
247 propKind = optPropKind::OPK_ARRAYLEN;
248 }
249 else if (tree->OperIsIndir())
250 {
251 // optFoldNullCheck takes care of updating statement info if a null check is removed.
252 optFoldNullCheck(tree);
253
254 if (gtIsVtableRef(tree))
255 {
256 // Don't propagate type handles that are used as null checks, which are usually in
257 // form of
258 // * stmtExpr void (top level)
259 // \--* indir int
260 // \--* lclVar ref V02 loc0
261 if (compCurStmt->gtStmt.gtStmtExpr == tree)
262 {
263 return nullptr;
264 }
265
266 objectRefPtr = tree->AsIndir()->Addr();
267 propKind = optPropKind::OPK_OBJ_GETTYPE;
268 }
269 else
270 {
271 return nullptr;
272 }
273 }
274 else
275 {
276 return nullptr;
277 }
278
279 if (!objectRefPtr->OperIsScalarLocal() || !lvaInSsa(objectRefPtr->AsLclVarCommon()->GetLclNum()))
280
281 {
282 return nullptr;
283 }
284
285 unsigned lclNum = objectRefPtr->AsLclVarCommon()->GetLclNum();
286 unsigned ssaNum = objectRefPtr->AsLclVarCommon()->GetSsaNum();
287 GenTree* actualVal = optPropGetValue(lclNum, ssaNum, propKind);
288
289 if (actualVal != nullptr)
290 {
291 assert((propKind == optPropKind::OPK_ARRAYLEN) || (propKind == optPropKind::OPK_OBJ_GETTYPE));
292 assert(actualVal->IsCnsIntOrI());
293#if SMALL_TREE_NODES
294 assert(actualVal->GetNodeSize() == TREE_NODE_SZ_SMALL);
295#endif
296
297 ssize_t actualConstVal = actualVal->AsIntCon()->IconValue();
298
299 if (propKind == optPropKind::OPK_ARRAYLEN)
300 {
301 if ((actualConstVal < 0) || (actualConstVal > INT32_MAX))
302 {
303 // Don't propagate array lengths that are beyond the maximum value of a GT_ARR_LENGTH or negative.
304 // node. CORINFO_HELP_NEWARR_1_OBJ helper call allows to take a long integer as the
305 // array length argument, but the type of GT_ARR_LENGTH is always INT32.
306 return nullptr;
307 }
308
309 // When replacing GT_ARR_LENGTH nodes with constants we can end up with GT_ARR_BOUNDS_CHECK
310 // nodes that have constant operands and thus can be trivially proved to be useless. It's
311 // better to remove these range checks here, otherwise they'll pass through assertion prop
312 // (creating useless (c1 < c2)-like assertions) and reach RangeCheck where they are finally
313 // removed. Common patterns like new int[] { x, y, z } benefit from this.
314
315 if ((tree->gtNext != nullptr) && tree->gtNext->OperIs(GT_ARR_BOUNDS_CHECK))
316 {
317 GenTreeBoundsChk* check = tree->gtNext->AsBoundsChk();
318
319 if ((check->gtArrLen == tree) && check->gtIndex->IsCnsIntOrI())
320 {
321 ssize_t checkConstVal = check->gtIndex->AsIntCon()->IconValue();
322 if ((checkConstVal >= 0) && (checkConstVal < actualConstVal))
323 {
324 GenTree* comma = check->gtGetParent(nullptr);
325 if ((comma != nullptr) && comma->OperIs(GT_COMMA) && (comma->gtGetOp1() == check))
326 {
327 GenTree* next = check->gtNext;
328 optRemoveRangeCheck(comma, compCurStmt);
329 // Both `tree` and `check` have been removed from the statement.
330 // 'tree' was replaced with 'nop' or side effect list under 'comma'.
331 return comma->gtGetOp1();
332 }
333 }
334 }
335 }
336 }
337
338#ifdef DEBUG
339 if (verbose)
340 {
341 printf("optEarlyProp Rewriting " FMT_BB "\n", compCurBB->bbNum);
342 gtDispTree(compCurStmt);
343 printf("\n");
344 }
345#endif
346
347 GenTree* actualValClone = gtCloneExpr(actualVal);
348
349 if (actualValClone->gtType != tree->gtType)
350 {
351 assert(actualValClone->gtType == TYP_LONG);
352 assert(tree->gtType == TYP_INT);
353 assert((actualConstVal >= 0) && (actualConstVal <= INT32_MAX));
354 actualValClone->gtType = tree->gtType;
355 }
356
357 // Propagating a constant into an array index expression requires calling
358 // LabelIndex to update the FieldSeq annotations. EarlyProp may replace
359 // array length expressions with constants, so check if this is an array
360 // length operator that is part of an array index expression.
361 bool isIndexExpr = (tree->OperGet() == GT_ARR_LENGTH && ((tree->gtFlags & GTF_ARRLEN_ARR_IDX) != 0));
362 if (isIndexExpr)
363 {
364 actualValClone->LabelIndex(this);
365 }
366
367 // actualValClone has small tree node size, it is safe to use CopyFrom here.
368 tree->ReplaceWith(actualValClone, this);
369
370#ifdef DEBUG
371 if (verbose)
372 {
373 printf("to\n");
374 gtDispTree(compCurStmt);
375 printf("\n");
376 }
377#endif
378 return tree;
379 }
380
381 return nullptr;
382}
383
384//-------------------------------------------------------------------------------------------
385// optPropGetValue: Given an SSA object ref pointer, get the value needed based on valueKind.
386//
387// Arguments:
388// lclNum - The local var number of the ref pointer.
389// ssaNum - The SSA var number of the ref pointer.
390// valueKind - The kind of value of interest.
391//
392// Return Value:
393// Return the corresponding value based on valueKind.
394
395GenTree* Compiler::optPropGetValue(unsigned lclNum, unsigned ssaNum, optPropKind valueKind)
396{
397 return optPropGetValueRec(lclNum, ssaNum, valueKind, 0);
398}
399
400//-----------------------------------------------------------------------------------
401// optPropGetValueRec: Given an SSA object ref pointer, get the value needed based on valueKind
402// within a recursion bound.
403//
404// Arguments:
405// lclNum - The local var number of the array pointer.
406// ssaNum - The SSA var number of the array pointer.
407// valueKind - The kind of value of interest.
408// walkDepth - Current recursive walking depth.
409//
410// Return Value:
411// Return the corresponding value based on valueKind.
412
413GenTree* Compiler::optPropGetValueRec(unsigned lclNum, unsigned ssaNum, optPropKind valueKind, int walkDepth)
414{
415 if (ssaNum == SsaConfig::RESERVED_SSA_NUM)
416 {
417 return nullptr;
418 }
419
420 SSAName ssaName(lclNum, ssaNum);
421 GenTree* value = nullptr;
422
423 // Bound the recursion with a hard limit.
424 if (walkDepth > optEarlyPropRecurBound)
425 {
426 return nullptr;
427 }
428
429 // Track along the use-def chain to get the array length
430 GenTree* treelhs = lvaTable[lclNum].GetPerSsaData(ssaNum)->m_defLoc.m_tree;
431
432 if (treelhs == nullptr)
433 {
434 // Incoming parameters or live-in variables don't have actual definition tree node
435 // for their FIRST_SSA_NUM. See SsaBuilder::RenameVariables.
436 assert(ssaNum == SsaConfig::FIRST_SSA_NUM);
437 }
438 else
439 {
440 GenTree** lhsPtr;
441 GenTree* treeDefParent = treelhs->gtGetParent(&lhsPtr);
442
443 if (treeDefParent->OperGet() == GT_ASG)
444 {
445 assert(treelhs == treeDefParent->gtGetOp1());
446 GenTree* treeRhs = treeDefParent->gtGetOp2();
447
448 if (treeRhs->OperIsScalarLocal() && lvaInSsa(treeRhs->AsLclVarCommon()->GetLclNum()))
449 {
450 // Recursively track the Rhs
451 unsigned rhsLclNum = treeRhs->AsLclVarCommon()->GetLclNum();
452 unsigned rhsSsaNum = treeRhs->AsLclVarCommon()->GetSsaNum();
453
454 value = optPropGetValueRec(rhsLclNum, rhsSsaNum, valueKind, walkDepth + 1);
455 }
456 else
457 {
458 if (valueKind == optPropKind::OPK_ARRAYLEN)
459 {
460 value = getArrayLengthFromAllocation(treeRhs);
461 if (value != nullptr)
462 {
463 if (!value->IsCnsIntOrI())
464 {
465 // Leave out non-constant-sized array
466 value = nullptr;
467 }
468 }
469 }
470 else if (valueKind == optPropKind::OPK_OBJ_GETTYPE)
471 {
472 value = getObjectHandleNodeFromAllocation(treeRhs);
473 if (value != nullptr)
474 {
475 if (!value->IsCnsIntOrI())
476 {
477 // Leave out non-constant-sized array
478 value = nullptr;
479 }
480 }
481 }
482 }
483 }
484 }
485
486 return value;
487}
488
489//----------------------------------------------------------------
490// optFoldNullChecks: Try to find a GT_NULLCHECK node that can be folded into the GT_INDIR node.
491//
492// Arguments:
493// tree - The input GT_INDIR tree.
494//
495
496void Compiler::optFoldNullCheck(GenTree* tree)
497{
498 //
499 // Check for a pattern like this:
500 //
501 // =
502 // / \
503 // x comma
504 // / \
505 // nullcheck +
506 // | / \
507 // y y const
508 //
509 //
510 // some trees in the same
511 // basic block with
512 // no unsafe side effects
513 //
514 // indir
515 // |
516 // x
517 //
518 // where the const is suitably small
519 // and transform it into
520 //
521 // =
522 // / \
523 // x +
524 // / \
525 // y const
526 //
527 //
528 // some trees with no unsafe side effects here
529 //
530 // indir
531 // |
532 // x
533
534 if ((compCurBB->bbFlags & BBF_HAS_NULLCHECK) == 0)
535 {
536 return;
537 }
538
539 assert(tree->OperIsIndir());
540
541 GenTree* const addr = tree->AsIndir()->Addr();
542 if (addr->OperGet() == GT_LCL_VAR)
543 {
544 // Check if we have the pattern above and find the nullcheck node if we do.
545
546 // Find the definition of the indirected local (x in the picture)
547 GenTreeLclVarCommon* const lclVarNode = addr->AsLclVarCommon();
548
549 const unsigned lclNum = lclVarNode->GetLclNum();
550 const unsigned ssaNum = lclVarNode->GetSsaNum();
551
552 if (ssaNum != SsaConfig::RESERVED_SSA_NUM)
553 {
554 DefLoc defLoc = lvaTable[lclNum].GetPerSsaData(ssaNum)->m_defLoc;
555 BasicBlock* defBlock = defLoc.m_blk;
556
557 if (compCurBB == defBlock)
558 {
559 GenTree* defTree = defLoc.m_tree;
560 GenTree* defParent = defTree->gtGetParent(nullptr);
561
562 if ((defParent->OperGet() == GT_ASG) && (defParent->gtNext == nullptr))
563 {
564 GenTree* defRHS = defParent->gtGetOp2();
565 if (defRHS->OperGet() == GT_COMMA)
566 {
567 if (defRHS->gtGetOp1()->OperGet() == GT_NULLCHECK)
568 {
569 GenTree* nullCheckTree = defRHS->gtGetOp1();
570 if (nullCheckTree->gtGetOp1()->OperGet() == GT_LCL_VAR)
571 {
572 // We found a candidate for 'y' in the picture
573 unsigned nullCheckLclNum = nullCheckTree->gtGetOp1()->AsLclVarCommon()->GetLclNum();
574
575 if (defRHS->gtGetOp2()->OperGet() == GT_ADD)
576 {
577 GenTree* additionNode = defRHS->gtGetOp2();
578 if ((additionNode->gtGetOp1()->OperGet() == GT_LCL_VAR) &&
579 (additionNode->gtGetOp1()->gtLclVarCommon.gtLclNum == nullCheckLclNum))
580 {
581 GenTree* offset = additionNode->gtGetOp2();
582 if (offset->IsCnsIntOrI())
583 {
584 if (!fgIsBigOffset(offset->gtIntConCommon.IconValue()))
585 {
586 // Walk from the use to the def in reverse execution order to see
587 // if any nodes have unsafe side effects.
588 GenTree* currentTree = lclVarNode->gtPrev;
589 bool isInsideTry = compCurBB->hasTryIndex();
590 bool canRemoveNullCheck = true;
591 const unsigned maxNodesWalked = 25;
592 unsigned nodesWalked = 0;
593
594 // First walk the nodes in the statement containing the indirection
595 // in reverse execution order starting with the indirection's
596 // predecessor.
597 while (canRemoveNullCheck && (currentTree != nullptr))
598 {
599 if ((nodesWalked++ > maxNodesWalked) ||
600 !optCanMoveNullCheckPastTree(currentTree, isInsideTry))
601 {
602 canRemoveNullCheck = false;
603 }
604 else
605 {
606 currentTree = currentTree->gtPrev;
607 }
608 }
609
610 // Then walk the statement list in reverse execution order
611 // until we get to the statement containing the null check.
612 // We only need to check the side effects at the root of each statement.
613 GenTree* curStmt = compCurStmt->gtPrev;
614 currentTree = curStmt->gtStmt.gtStmtExpr;
615 while (canRemoveNullCheck && (currentTree != defParent))
616 {
617 if ((nodesWalked++ > maxNodesWalked) ||
618 !optCanMoveNullCheckPastTree(currentTree, isInsideTry))
619 {
620 canRemoveNullCheck = false;
621 }
622 else
623 {
624 curStmt = curStmt->gtStmt.gtPrevStmt;
625 assert(curStmt != nullptr);
626 currentTree = curStmt->gtStmt.gtStmtExpr;
627 }
628 }
629
630 if (canRemoveNullCheck)
631 {
632 // Remove the null check
633 nullCheckTree->gtFlags &= ~(GTF_EXCEPT | GTF_DONT_CSE);
634
635 // Set this flag to prevent reordering
636 nullCheckTree->gtFlags |= GTF_ORDER_SIDEEFF;
637 nullCheckTree->gtFlags |= GTF_IND_NONFAULTING;
638
639 defRHS->gtFlags &= ~(GTF_EXCEPT | GTF_DONT_CSE);
640 defRHS->gtFlags |=
641 additionNode->gtFlags & (GTF_EXCEPT | GTF_DONT_CSE);
642
643 // Re-morph the statement.
644 fgMorphBlockStmt(compCurBB,
645 curStmt->AsStmt() DEBUGARG("optFoldNullCheck"));
646 }
647 }
648 }
649 }
650 }
651 }
652 }
653 }
654 }
655 }
656 }
657 }
658}
659
660//----------------------------------------------------------------
661// optCanMoveNullCheckPastTree: Check if GT_NULLCHECK can be folded into a node that
662// is after tree is execution order.
663//
664// Arguments:
665// tree - The input GT_INDIR tree.
666// isInsideTry - True if tree is inside try, false otherwise
667//
668// Return Value:
669// True if GT_NULLCHECK can be folded into a node that is after tree is execution order,
670// false otherwise.
671
672bool Compiler::optCanMoveNullCheckPastTree(GenTree* tree, bool isInsideTry)
673{
674 bool result = true;
675 if (isInsideTry)
676 {
677 // We disallow calls, exception sources, and all assignments.
678 // Assignments to locals are disallowed inside try because
679 // they may be live in the handler.
680 if ((tree->gtFlags & GTF_SIDE_EFFECT) != 0)
681 {
682 result = false;
683 }
684 }
685 else
686 {
687 // We disallow calls, exception sources, and assignments to
688 // global memory.
689 if (GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS(tree->gtFlags))
690 {
691 result = false;
692 }
693 }
694 return result;
695}
696