diff --git a/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs b/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs index 8e8fda213e32..d91fb09dd920 100644 --- a/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs +++ b/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs @@ -806,6 +806,9 @@ public bool ApplyTemporaryInlining (in UnreachableBlocksOptimizer optimizer) return changed; } + static bool IsConditionalBranch (OpCode opCode) + => opCode.Code is Code.Brfalse or Code.Brfalse_S or Code.Brtrue or Code.Brtrue_S; + void RemoveUnreachableInstructions (BitArray reachable) { LinkerILProcessor processor = Body.GetLinkerILProcessor (); @@ -815,8 +818,22 @@ void RemoveUnreachableInstructions (BitArray reachable) if (reachable[i]) continue; - processor.RemoveAt (i - removed); - ++removed; + int index = i - removed; + // If we intend to remove the last instruction we replaced it with "ret" above (not "nop") + // but we can't get rid of it completely because it may happen that the last kept instruction + // is a conditional branch - in which case to keep the IL valid, there has to be something after + // the conditional branch instruction (the else branch). So if that's the case + // inject "ldnull; throw;" at the end - this branch should never be reachable and it's always valid + // (ret may need to return a value of the right type if the method has a return value which is complicated + // to construct out of nothing). + if (index == Body.Instructions.Count - 1 && Body.Instructions[index].OpCode == OpCodes.Ret && + index > 0 && IsConditionalBranch (Body.Instructions[index - 1].OpCode)) { + processor.Replace (index, Instruction.Create (OpCodes.Ldnull)); + processor.InsertAfter (Body.Instructions[index], Instruction.Create (OpCodes.Throw)); + } else { + processor.RemoveAt (index); + ++removed; + } } } diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedJumpTarget.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedJumpTarget.cs index 5c7ee375fe1b..90d9a3e7ea2c 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedJumpTarget.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedJumpTarget.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection.Emit; using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; @@ -12,6 +11,7 @@ public class ReplacedJumpTarget public static void Main () { Test_1 (int.Parse ("91")); + TestRemovedLastBranch (int.Parse ("92")); } [Kept] @@ -73,5 +73,36 @@ static int Test_1 (int value) } static int Value => 2; + + [Kept] + [ExpectedInstructionSequence (new[] { + "br.s il_3", + "ret", + "ldc.i4.1", + "brtrue.s il_2", + "ldnull", + "throw" + })] + static void TestRemovedLastBranch (int param) + { + goto DoWork; + + ReturnFromMethod: + return; + + DoWork: + if (AlwaysTrue) { + goto ReturnFromMethod; + } else { // This branch will be removed + DoSomething (); + goto ReturnFromMethod; + } + } + + static void DoSomething () + { + } + + static bool AlwaysTrue => true; } } \ No newline at end of file