/// <summary>
 /// Matches Roslyn C# switch on nullable.
 /// </summary>
 bool MatchRoslynSwitchOnNullable(InstructionCollection <ILInstruction> instructions, int i, out SwitchInstruction newSwitch)
 {
     newSwitch = null;
     // match first block:
     // stloc tmp(ldloc switchValueVar)
     // if (logic.not(call get_HasValue(ldloca tmp))) br nullCaseBlock
     // br switchBlock
     if (i < 1)
     {
         return(false);
     }
     if (!instructions[i - 1].MatchStLoc(out var tmp, out var switchValue) ||
         !instructions[i].MatchIfInstruction(out var condition, out var trueInst))
     {
         return(false);
     }
     if (tmp.StoreCount != 1 || tmp.AddressCount != 2 || tmp.LoadCount != 0)
     {
         return(false);
     }
     if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock))
     {
         return(false);
     }
     if (!condition.MatchLogicNot(out var getHasValue) || !NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILVariable target1) || target1 != tmp)
     {
         return(false);
     }
     // match second block: switchBlock
     // stloc switchVar(call GetValueOrDefault(ldloca tmp))
     // switch (ldloc switchVar) {
     //  case [0..1): br caseBlock1
     // ... more cases ...
     //  case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock
     // }
     if (switchBlock.Instructions.Count != 2 || switchBlock.IncomingEdgeCount != 1)
     {
         return(false);
     }
     if (!switchBlock.Instructions[0].MatchStLoc(out var switchVar, out var getValueOrDefault))
     {
         return(false);
     }
     if (!switchVar.IsSingleDefinition || switchVar.LoadCount != 1)
     {
         return(false);
     }
     if (!NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefault, tmp))
     {
         return(false);
     }
     if (!(switchBlock.Instructions[1] is SwitchInstruction switchInst))
     {
         return(false);
     }
     newSwitch = BuildLiftedSwitch(nullCaseBlock, switchInst, switchValue);
     return(true);
 }
Example #2
0
        bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck)
        {
            numObjVarLoadsInCheck = 2;
            CallVirt callVirt;

            if (objVar.Type.IsKnownType(KnownTypeCode.NullableOfT))
            {
                if (!checkInst.MatchIfInstruction(out var condition, out var disposeInst))
                {
                    return(false);
                }
                if (!NullableLiftingTransform.MatchHasValueCall(condition, objVar))
                {
                    return(false);
                }
                if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
                {
                    return(false);
                }
                if (!(disposeBlock.Instructions[0] is CallVirt cv))
                {
                    return(false);
                }
                callVirt = cv;
                if (callVirt.Method.FullName != "System.IDisposable.Dispose")
                {
                    return(false);
                }
                if (callVirt.Method.Parameters.Count > 0)
                {
                    return(false);
                }
                if (callVirt.Arguments.Count != 1)
                {
                    return(false);
                }
                var firstArg = cv.Arguments.FirstOrDefault();
                if (!(firstArg.MatchUnboxAny(out var innerArg1, out var unboxType) && unboxType.IsKnownType(KnownTypeCode.IDisposable)))
                {
                    if (!firstArg.MatchAddressOf(out var innerArg2))
                    {
                        return(false);
                    }
                    return(NullableLiftingTransform.MatchGetValueOrDefault(innerArg2, objVar));
                }
                else
                {
                    if (!(innerArg1.MatchBox(out firstArg, out var boxType) && boxType.IsKnownType(KnownTypeCode.NullableOfT) &&
                          NullableType.GetUnderlyingType(boxType).Equals(NullableType.GetUnderlyingType(objVar.Type))))
                    {
                        return(false);
                    }
                    return(firstArg.MatchLdLoc(objVar));
                }
            }
Example #3
0
        /// <summary>
        /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable.
        /// </summary>
        /// <param name="next">The next top-level expression</param>
        /// <param name="v">The variable being eliminated by inlining.</param>
        /// <param name="inlinedExpression">The expression being inlined</param>
        static bool NonAggressiveInlineInto(ILInstruction next, FindResult findResult, ILInstruction inlinedExpression, ILVariable v)
        {
            if (findResult.Type == FindResultType.NamedArgument)
            {
                var originalStore = (StLoc)inlinedExpression.Parent;
                return(!originalStore.ILStackWasEmpty);
            }
            Debug.Assert(findResult.Type == FindResultType.Found);

            var loadInst = findResult.LoadInst;

            Debug.Assert(loadInst.IsDescendantOf(next));

            // decide based on the source expression being inlined
            switch (inlinedExpression.OpCode)
            {
            case OpCode.DefaultValue:
            case OpCode.StObj:
            case OpCode.NumericCompoundAssign:
            case OpCode.UserDefinedCompoundAssign:
            case OpCode.Await:
            case OpCode.SwitchInstruction:
                return(true);

            case OpCode.LdLoc:
                if (v.StateMachineField == null && ((LdLoc)inlinedExpression).Variable.StateMachineField != null)
                {
                    // Roslyn likes to put the result of fetching a state machine field into a temporary variable,
                    // so inline more aggressively in such cases.
                    return(true);
                }
                break;
            }
            if (inlinedExpression.ResultType == StackType.Ref)
            {
                // VB likes to use ref locals for compound assignment
                // (the C# compiler uses ref stack slots instead).
                // We want to avoid unnecessary ref locals, so we'll always inline them if possible.
                return(true);
            }

            var parent = loadInst.Parent;

            if (NullableLiftingTransform.MatchNullableCtor(parent, out _, out _))
            {
                // inline into nullable ctor call in lifted operator
                parent = parent.Parent;
            }
            if (parent is ILiftableInstruction liftable && liftable.IsLifted)
            {
                return(true);                // inline into lifted operators
            }
            // decide based on the new parent into which we are inlining:
            switch (parent.OpCode)
            {
            case OpCode.NullCoalescingInstruction:
                if (NullableType.IsNullable(v.Type))
                {
                    return(true);                            // inline nullables into ?? operator
                }
                break;

            case OpCode.NullableUnwrap:
                return(true);                        // inline into ?. operator

            case OpCode.UserDefinedLogicOperator:
            case OpCode.DynamicLogicOperatorInstruction:
                return(true);                        // inline into (left slot of) user-defined && or || operator

            case OpCode.DynamicGetMemberInstruction:
            case OpCode.DynamicGetIndexInstruction:
                if (parent.Parent.OpCode == OpCode.DynamicCompoundAssign)
                {
                    return(true);                            // inline into dynamic compound assignments
                }
                break;

            case OpCode.DynamicCompoundAssign:
                return(true);

            case OpCode.GetPinnableReference:
            case OpCode.LocAllocSpan:
                return(true);                        // inline size-expressions into localloc.span

            case OpCode.Call:
            case OpCode.CallVirt:
                // Aggressive inline into property/indexer getter calls for compound assignment calls
                // (The compiler generates locals for these because it doesn't want to evalute the args twice for getter+setter)
                if (parent.SlotInfo == CompoundAssignmentInstruction.TargetSlot)
                {
                    return(true);
                }
                if (((CallInstruction)parent).Method is SyntheticRangeIndexAccessor)
                {
                    return(true);
                }
                break;

            case OpCode.CallIndirect when loadInst.SlotInfo == CallIndirect.FunctionPointerSlot:
                return(true);

            case OpCode.LdElema:
                if (((LdElema)parent).WithSystemIndex)
                {
                    return(true);
                }
                break;

            case OpCode.Leave:
            case OpCode.YieldReturn:
                return(true);

            case OpCode.SwitchInstruction:
            //case OpCode.BinaryNumericInstruction when parent.SlotInfo == SwitchInstruction.ValueSlot:
            case OpCode.StringToInt when parent.SlotInfo == SwitchInstruction.ValueSlot:
                return(true);

            case OpCode.MatchInstruction when((MatchInstruction)parent).IsDeconstructTuple:
                return(true);
            }
            // decide based on the top-level target instruction into which we are inlining:
            switch (next.OpCode)
            {
            case OpCode.IfInstruction:
                while (parent.MatchLogicNot(out _))
                {
                    parent = parent.Parent;
                }
                return(parent == next);

            default:
                return(false);
            }
        }
Example #4
0
        /// <summary>
        /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable.
        /// </summary>
        /// <param name="next">The next top-level expression</param>
        /// <param name="loadInst">The load within 'next'</param>
        /// <param name="inlinedExpression">The expression being inlined</param>
        static bool NonAggressiveInlineInto(ILInstruction next, ILInstruction loadInst, ILInstruction inlinedExpression, ILVariable v)
        {
            Debug.Assert(loadInst.IsDescendantOf(next));

            // decide based on the source expression being inlined
            switch (inlinedExpression.OpCode)
            {
            case OpCode.DefaultValue:
            case OpCode.StObj:
            case OpCode.NumericCompoundAssign:
            case OpCode.UserDefinedCompoundAssign:
            case OpCode.Await:
                return(true);

            case OpCode.LdLoc:
                if (v.StateMachineField == null && ((LdLoc)inlinedExpression).Variable.StateMachineField != null)
                {
                    // Roslyn likes to put the result of fetching a state machine field into a temporary variable,
                    // so inline more aggressively in such cases.
                    return(true);
                }
                break;
            }

            var parent = loadInst.Parent;

            if (NullableLiftingTransform.MatchNullableCtor(parent, out _, out _))
            {
                // inline into nullable ctor call in lifted operator
                parent = parent.Parent;
            }
            if (parent is ILiftableInstruction liftable && liftable.IsLifted)
            {
                return(true);                // inline into lifted operators
            }
            switch (parent.OpCode)
            {
            case OpCode.NullCoalescingInstruction:
                if (NullableType.IsNullable(v.Type))
                {
                    return(true);                            // inline nullables into ?? operator
                }
                break;

            case OpCode.NullableUnwrap:
                return(true);                        // inline into ?. operator

            case OpCode.UserDefinedLogicOperator:
            case OpCode.DynamicLogicOperatorInstruction:
                return(true);                        // inline into (left slot of) user-defined && or || operator

            case OpCode.DynamicGetMemberInstruction:
            case OpCode.DynamicGetIndexInstruction:
            case OpCode.LdObj:
                if (parent.Parent.OpCode == OpCode.DynamicCompoundAssign)
                {
                    return(true);                            // inline into dynamic compound assignments
                }
                break;
            }
            // decide based on the target into which we are inlining
            switch (next.OpCode)
            {
            case OpCode.Leave:
            case OpCode.YieldReturn:
                return(parent == next);

            case OpCode.IfInstruction:
                while (parent.MatchLogicNot(out _))
                {
                    parent = parent.Parent;
                }
                return(parent == next);

            case OpCode.BlockContainer:
                if (((BlockContainer)next).EntryPoint.Instructions[0] is SwitchInstruction switchInst)
                {
                    next = switchInst;
                    goto case OpCode.SwitchInstruction;
                }
                else
                {
                    return(false);
                }

            case OpCode.SwitchInstruction:
                return(parent == next || (parent.MatchBinaryNumericInstruction(BinaryNumericOperator.Sub) && parent.Parent == next));

            default:
                return(false);
            }
        }
        bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull)
        {
            var entryPoint = container.EntryPoint;

            if (entryPoint.Instructions.Count < 2 || entryPoint.Instructions.Count > 3 || entryPoint.IncomingEdgeCount != 1)
            {
                return(false);
            }
            int  leaveIndex  = entryPoint.Instructions.Count == 2 ? 1 : 2;
            int  checkIndex  = entryPoint.Instructions.Count == 2 ? 0 : 1;
            int  castIndex   = entryPoint.Instructions.Count == 3 ? 0 : -1;
            bool isReference = objVar.Type.IsReferenceType != false;

            if (castIndex > -1)
            {
                if (!entryPoint.Instructions[castIndex].MatchStLoc(out var tempVar, out var isinst))
                {
                    return(false);
                }
                if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) || !disposableType.IsKnownType(KnownTypeCode.IDisposable))
                {
                    return(false);
                }
                if (tempVar.StoreCount != 1 || tempVar.LoadCount != 2)
                {
                    return(false);
                }
                objVar      = tempVar;
                isReference = true;
            }
            if (!entryPoint.Instructions[leaveIndex].MatchLeave(container, out var returnValue) || !returnValue.MatchNop())
            {
                return(false);
            }
            CallVirt callVirt;

            if (objVar.Type.IsKnownType(KnownTypeCode.NullableOfT))
            {
                if (!entryPoint.Instructions[checkIndex].MatchIfInstruction(out var condition, out var disposeInst))
                {
                    return(false);
                }
                if (!NullableLiftingTransform.MatchHasValueCall(condition, objVar))
                {
                    return(false);
                }
                if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
                {
                    return(false);
                }
                if (!(disposeBlock.Instructions[0] is CallVirt cv))
                {
                    return(false);
                }
                callVirt = cv;
                if (callVirt.Method.FullName != "System.IDisposable.Dispose")
                {
                    return(false);
                }
                if (callVirt.Method.Parameters.Count > 0)
                {
                    return(false);
                }
                if (callVirt.Arguments.Count != 1)
                {
                    return(false);
                }
                var firstArg = cv.Arguments.FirstOrDefault();
                if (!(firstArg.MatchUnboxAny(out var innerArg1, out var unboxType) && unboxType.IsKnownType(KnownTypeCode.IDisposable)))
                {
                    if (!firstArg.MatchAddressOf(out var innerArg2))
                    {
                        return(false);
                    }
                    return(NullableLiftingTransform.MatchGetValueOrDefault(innerArg2, objVar));
                }
                else
                {
                    if (!(innerArg1.MatchBox(out firstArg, out var boxType) && boxType.IsKnownType(KnownTypeCode.NullableOfT) &&
                          NullableType.GetUnderlyingType(boxType).Equals(NullableType.GetUnderlyingType(objVar.Type))))
                    {
                        return(false);
                    }
                    return(firstArg.MatchLdLoc(objVar));
                }
            }
Example #6
0
        /// <summary>
        /// stloc v(value)
        /// if (logic.not(call get_HasValue(ldloca v))) throw(...)
        /// ... Call(arg1, arg2, call GetValueOrDefault(ldloca v), arg4) ...
        /// =>
        /// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ...
        /// </summary>
        bool TransformThrowExpressionValueTypes(Block block, int pos, StatementTransformContext context)
        {
            if (pos + 2 >= block.Instructions.Count)
            {
                return(false);
            }
            if (!(block.Instructions[pos] is StLoc stloc))
            {
                return(false);
            }
            ILVariable v = stloc.Variable;

            if (!(v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 2))
            {
                return(false);
            }
            if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst))
            {
                return(false);
            }
            if (!(Block.Unwrap(trueInst) is Throw throwInst))
            {
                return(false);
            }
            if (!condition.MatchLogicNot(out var arg))
            {
                return(false);
            }
            if (!(arg is CallInstruction call && NullableLiftingTransform.MatchHasValueCall(call, v)))
            {
                return(false);
            }
            var throwInstParent         = throwInst.Parent;
            var throwInstChildIndex     = throwInst.ChildIndex;
            var nullCoalescingWithThrow = new NullCoalescingInstruction(
                NullCoalescingKind.NullableWithValueFallback,
                stloc.Value,
                throwInst);
            var resultType = NullableType.GetUnderlyingType(call.Method.DeclaringType).GetStackType();

            nullCoalescingWithThrow.UnderlyingResultType = resultType;
            var result = ILInlining.FindLoadInNext(block.Instructions[pos + 2], v, nullCoalescingWithThrow, InliningOptions.None);

            if (result.Type == ILInlining.FindResultType.Found &&
                NullableLiftingTransform.MatchGetValueOrDefault(result.LoadInst.Parent, v))
            {
                context.Step("NullCoalescingTransform (value types + throw expression)", stloc);
                throwInst.resultType = resultType;
                result.LoadInst.Parent.ReplaceWith(nullCoalescingWithThrow);
                block.Instructions.RemoveRange(pos, 2);                 // remove store(s) and if instruction
                return(true);
            }
            else
            {
                // reset the primary position (see remarks on ILInstruction.Parent)
                stloc.Value = stloc.Value;
                var children = throwInstParent.Children;
                children[throwInstChildIndex] = throwInst;
                return(false);
            }
        }
        /// <summary>
        /// Matches Roslyn C# switch on nullable.
        /// </summary>
        bool MatchRoslynSwitchOnNullable(InstructionCollection <ILInstruction> instructions, int i, out SwitchInstruction newSwitch)
        {
            newSwitch = null;
            // match first block:
            // stloc tmp(ldloc switchValueVar)
            // if (logic.not(call get_HasValue(ldloca tmp))) br nullCaseBlock
            // br switchBlock
            if (i < 1)
            {
                return(false);
            }
            if (!instructions[i - 1].MatchStLoc(out var tmp, out var switchValue) ||
                !instructions[i].MatchIfInstruction(out var condition, out var trueInst))
            {
                return(false);
            }
            if (tmp.StoreCount != 1 || tmp.AddressCount != 2 || tmp.LoadCount != 0)
            {
                return(false);
            }
            if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock))
            {
                return(false);
            }
            if (!condition.MatchLogicNot(out var getHasValue) || !NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILVariable target1) || target1 != tmp)
            {
                return(false);
            }
            // match second block: switchBlock
            // note: I have seen cases where switchVar is inlined into the switch.
            // stloc switchVar(call GetValueOrDefault(ldloca tmp))
            // switch (ldloc switchVar) {
            //  case [0..1): br caseBlock1
            // ... more cases ...
            //  case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock
            // }
            if (switchBlock.IncomingEdgeCount != 1)
            {
                return(false);
            }
            SwitchInstruction switchInst;

            switch (switchBlock.Instructions.Count)
            {
            case 2: {
                // this is the normal case described by the pattern above
                if (!switchBlock.Instructions[0].MatchStLoc(out var switchVar, out var getValueOrDefault))
                {
                    return(false);
                }
                if (!switchVar.IsSingleDefinition || switchVar.LoadCount != 1)
                {
                    return(false);
                }
                if (!NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefault, tmp))
                {
                    return(false);
                }
                if (!(switchBlock.Instructions[1] is SwitchInstruction si))
                {
                    return(false);
                }
                switchInst = si;
                break;
            }

            case 1: {
                // this is the special case where `call GetValueOrDefault(ldloca tmp)` is inlined into the switch.
                if (!(switchBlock.Instructions[0] is SwitchInstruction si))
                {
                    return(false);
                }
                if (!NullableLiftingTransform.MatchGetValueOrDefault(si.Value, tmp))
                {
                    return(false);
                }
                switchInst = si;
                break;
            }

            default: {
                return(false);
            }
            }
            newSwitch = BuildLiftedSwitch(nullCaseBlock, switchInst, switchValue);
            return(true);
        }
        /// <summary>
        /// Matches Roslyn C# switch on nullable.
        /// </summary>
        bool MatchRoslynSwitchOnNullable(InstructionCollection <ILInstruction> instructions, int i, out SwitchInstruction newSwitch)
        {
            newSwitch = null;
            // match first block:
            // if (logic.not(call get_HasValue(target))) br nullCaseBlock
            // br switchBlock
            if (!instructions[i].MatchIfInstruction(out var condition, out var trueInst))
            {
                return(false);
            }
            if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock))
            {
                return(false);
            }
            if (!condition.MatchLogicNot(out var getHasValue) || !NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILInstruction target) || !SemanticHelper.IsPure(target.Flags))
            {
                return(false);
            }
            // match second block: switchBlock
            // note: I have seen cases where switchVar is inlined into the switch.
            // stloc switchVar(call GetValueOrDefault(ldloca tmp))
            // switch (ldloc switchVar) {
            //  case [0..1): br caseBlock1
            // ... more cases ...
            //  case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock
            // }
            if (switchBlock.IncomingEdgeCount != 1)
            {
                return(false);
            }
            SwitchInstruction switchInst;

            switch (switchBlock.Instructions.Count)
            {
            case 2:
            {
                // this is the normal case described by the pattern above
                if (!switchBlock.Instructions[0].MatchStLoc(out var switchVar, out var getValueOrDefault))
                {
                    return(false);
                }
                if (!switchVar.IsSingleDefinition || switchVar.LoadCount != 1)
                {
                    return(false);
                }
                if (!(NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefault, out ILInstruction target2) && target2.Match(target).Success))
                {
                    return(false);
                }
                if (!(switchBlock.Instructions[1] is SwitchInstruction si))
                {
                    return(false);
                }
                switchInst = si;
                break;
            }

            case 1:
            {
                // this is the special case where `call GetValueOrDefault(ldloca tmp)` is inlined into the switch.
                if (!(switchBlock.Instructions[0] is SwitchInstruction si))
                {
                    return(false);
                }
                if (!(NullableLiftingTransform.MatchGetValueOrDefault(si.Value, out ILInstruction target2) && target2.Match(target).Success))
                {
                    return(false);
                }
                switchInst = si;
                break;
            }

            default:
            {
                return(false);
            }
            }
            ILInstruction switchValue;

            if (target.MatchLdLoca(out var v))
            {
                switchValue = new LdLoc(v).WithILRange(target);
            }
            else
            {
                switchValue = new LdObj(target, ((CallInstruction)getHasValue).Method.DeclaringType);
            }
            newSwitch = BuildLiftedSwitch(nullCaseBlock, switchInst, switchValue);
            return(true);
        }
Example #9
0
        /// <summary>
        /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable.
        /// </summary>
        /// <param name="next">The next top-level expression</param>
        /// <param name="v">The variable being eliminated by inlining.</param>
        /// <param name="inlinedExpression">The expression being inlined</param>
        static bool NonAggressiveInlineInto(ILInstruction next, FindResult findResult, ILInstruction inlinedExpression, ILVariable v)
        {
            if (findResult.Type == FindResultType.NamedArgument)
            {
                var originalStore = (StLoc)inlinedExpression.Parent;
                return(!originalStore.ILStackWasEmpty);
            }
            Debug.Assert(findResult.Type == FindResultType.Found);

            var loadInst = findResult.LoadInst;

            Debug.Assert(loadInst.IsDescendantOf(next));

            // decide based on the source expression being inlined
            switch (inlinedExpression.OpCode)
            {
            case OpCode.DefaultValue:
            case OpCode.StObj:
            case OpCode.NumericCompoundAssign:
            case OpCode.UserDefinedCompoundAssign:
            case OpCode.Await:
                return(true);

            case OpCode.LdLoc:
                if (v.StateMachineField == null && ((LdLoc)inlinedExpression).Variable.StateMachineField != null)
                {
                    // Roslyn likes to put the result of fetching a state machine field into a temporary variable,
                    // so inline more aggressively in such cases.
                    return(true);
                }
                break;
            }

            var parent = loadInst.Parent;

            if (NullableLiftingTransform.MatchNullableCtor(parent, out _, out _))
            {
                // inline into nullable ctor call in lifted operator
                parent = parent.Parent;
            }
            if (parent is ILiftableInstruction liftable && liftable.IsLifted)
            {
                return(true);                // inline into lifted operators
            }
            // decide based on the new parent into which we are inlining:
            switch (parent.OpCode)
            {
            case OpCode.NullCoalescingInstruction:
                if (NullableType.IsNullable(v.Type))
                {
                    return(true);                            // inline nullables into ?? operator
                }
                break;

            case OpCode.NullableUnwrap:
                return(true);                        // inline into ?. operator

            case OpCode.UserDefinedLogicOperator:
            case OpCode.DynamicLogicOperatorInstruction:
                return(true);                        // inline into (left slot of) user-defined && or || operator

            case OpCode.DynamicGetMemberInstruction:
            case OpCode.DynamicGetIndexInstruction:
                if (parent.Parent.OpCode == OpCode.DynamicCompoundAssign)
                {
                    return(true);                            // inline into dynamic compound assignments
                }
                break;

            case OpCode.DynamicCompoundAssign:
                return(true);

            case OpCode.ArrayToPointer:
            case OpCode.LocAllocSpan:
                return(true);                        // inline size-expressions into localloc.span

            case OpCode.Call:
            case OpCode.CallVirt:
                // Aggressive inline into property/indexer getter calls for compound assignment calls
                // (The compiler generates locals for these because it doesn't want to evalute the args twice for getter+setter)
                if (parent.SlotInfo == CompoundAssignmentInstruction.TargetSlot)
                {
                    return(true);
                }
                break;
            }
            // decide based on the top-level target instruction into which we are inlining:
            switch (next.OpCode)
            {
            case OpCode.Leave:
            case OpCode.YieldReturn:
                return(parent == next);

            case OpCode.IfInstruction:
                while (parent.MatchLogicNot(out _))
                {
                    parent = parent.Parent;
                }
                return(parent == next);

            case OpCode.BlockContainer:
                if (((BlockContainer)next).EntryPoint.Instructions[0] is SwitchInstruction switchInst)
                {
                    next = switchInst;
                    goto case OpCode.SwitchInstruction;
                }
                else
                {
                    return(false);
                }

            case OpCode.SwitchInstruction:
                if (parent == next)
                {
                    return(true);
                }
                if (parent.MatchBinaryNumericInstruction(BinaryNumericOperator.Sub) && parent.Parent == next)
                {
                    return(true);
                }
                if (parent is StringToInt stringToInt && stringToInt.Parent == next)
                {
                    return(true);
                }
                return(false);

            default:
                return(false);
            }
        }