| File: Linker\LinkerILProcessor.cs | Web Access |
| Project: src\runtime\src\tools\illink\src\linker\Mono.Linker.csproj (illink) |
// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using Mono.Cecil; using Mono.Cecil.Cil; namespace Mono.Linker { #pragma warning disable RS0030 public sealed class LinkerILProcessor { readonly ILProcessor _ilProcessor; Collections.Generic.Collection<Instruction> Instructions => _ilProcessor.Body.Instructions; internal LinkerILProcessor(MethodBody body) { _ilProcessor = body.GetILProcessor(); } public void Emit(OpCode opcode) => Append(_ilProcessor.Create(opcode)); public void Emit(OpCode opcode, TypeReference type) => Append(_ilProcessor.Create(opcode, type)); public void Emit(OpCode opcode, MethodReference method) => Append(_ilProcessor.Create(opcode, method)); public void Emit(OpCode opcode, VariableDefinition variable) => Append(_ilProcessor.Create(opcode, variable)); public void Emit(OpCode opcode, string value) => Append(_ilProcessor.Create(opcode, value)); public void InsertBefore(Instruction target, Instruction instruction) { // When inserting before, all pointers have to be updated to the new "before" instruction. RedirectScopeStart(target, instruction); ReplaceInstructionReference(target, instruction); _ilProcessor.InsertBefore(target, instruction); } // When inserting after, no redirection is necessary since it will naturally be "appended" to the same blocks // as the target instruction. public void InsertAfter(Instruction target, Instruction instruction) { RedirectScopeEnd(target, instruction); _ilProcessor.InsertAfter(target, instruction); } public void Append(Instruction instruction) { Instruction? lastInstruction = Instructions.Count == 0 ? null : Instructions[Instructions.Count - 1]; RedirectScopeEnd(lastInstruction, instruction); _ilProcessor.Append(instruction); } public void Replace(Instruction target, Instruction instruction) { RedirectScopeStart(target, instruction); RedirectScopeEnd(target, instruction); ReplaceInstructionReference(target, instruction); _ilProcessor.Replace(target, instruction); } public void Replace(int index, Instruction instruction) => Replace(_ilProcessor.Body.Instructions[index], instruction); public void Remove(Instruction instruction) { int index = _ilProcessor.Body.Instructions.IndexOf(instruction); if (index == -1) throw new ArgumentOutOfRangeException(nameof(instruction)); Instruction? nextInstruction = Instructions.Count == index + 1 ? null : Instructions[index + 1]; Instruction? previousInstruction = index == 0 ? null : Instructions[index - 1]; RedirectScopeStart(instruction, nextInstruction); RedirectScopeEnd(instruction, previousInstruction); ReplaceInstructionReference(instruction, nextInstruction); _ilProcessor.Remove(instruction); } public void RemoveAt(int index) => Remove(Instructions[index]); void RedirectScopeStart(Instruction oldTarget, Instruction? newTarget) { // In Cecil "start" pointers point to the first instruction in a given scope // and the "end" pointers point to the first instruction after the given block // so they need to effectively point to the start of the next scope. // That's why both start and end are handled in the RedirectScopeStart. foreach (var handler in _ilProcessor.Body.ExceptionHandlers) { if (handler.TryStart == oldTarget) handler.TryStart = newTarget; if (handler.TryEnd == oldTarget) handler.TryEnd = newTarget; if (handler.HandlerStart == oldTarget) handler.HandlerStart = newTarget; if (handler.HandlerEnd == oldTarget) handler.HandlerEnd = newTarget; if (handler.FilterStart == oldTarget) handler.FilterStart = newTarget; } } #pragma warning disable IDE0060 // Remove unused parameter static void RedirectScopeEnd(Instruction? oldTarget, Instruction? newTarget) #pragma warning restore IDE0060 // Remove unused parameter { // Currently Cecil treats all block boundaries as "starts" // so nothing to do here. } void ReplaceInstructionReference(Instruction oldTarget, Instruction? newTarget) { foreach (var instr in Instructions) { switch (instr.OpCode.FlowControl) { case FlowControl.Cond_Branch: if (instr.Operand is Instruction?[] instructions) { for (int i = 0; i < instructions.Length; ++i) { if (instructions[i] == oldTarget) instructions[i] = newTarget; } break; } goto case FlowControl.Branch; case FlowControl.Branch: if (instr.Operand == oldTarget) instr.Operand = newTarget; break; } } } #pragma warning disable RS0030 } }