xamarin-macios/tests/linker/ILReader.cs

220 строки
6.2 KiB
C#
Исходник Обычный вид История

//
[mtouch/mmp] Give users more control over optimizations, and share more code between mtouch and mmp. (#3242) * [mtouch/mmp] Give users more control over optimizations, and share more code between mtouch and mmp. 1. Add an --optimize flag to mtouch/mmp that allows users to select which optimizations to apply (or not). This makes it easier to add future optimizations, and allow users to disable any optimization that causes problems without having to disable many other features. 2. Share as much optimization code as possible between mtouch and mmp. This immediately gives a benefit to mmp, which has three new optimizations only mtouch had: NSObject.IsDirectBinding inlining, IntPtr.Size inlining and dead code elimination. This results in ~6kb of disk space saved for a linked Xamarin.Mac app: * link sdk: [Debug][1], [Release][2] * link all: [Debug][3], [Release][4] Testing also verifies that monotouchtest ([Debug][5], [Release][6]) has not changed size at all, which means that no default optimizations have changed inadvertedly. [1]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-sdk-mac--debug [2]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-sdk-mac--release [3]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-all-mac--debug [4]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-all-mac--release [5]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#monotouchtest-iphonedebug64 [6]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#monotouchtest-iphonerelease64 * [tools] Don't enable the IsDirectBinding optimization by default for Xamarin.Mac apps, it's not safe. * Fix whitespace issues. * [doc] Document optimizations. * Officially support optimizations by adding them to the Versions.plist. * [linker] Improve IntPtr.Size inliner + dead code eliminatior and add tests. * Properly handle operands for the ldc_i4_s instruction (they're sbyte). * Fix less-than condition to actually do a less-than comparison. * Make sure to look up the bitness in the Target, not the Application, since the Application's value will be incorrect when building fat apps (both Is32Build and Is64Build will be true). * Remove unnecessary checks for the IntPtr.Size inliner: this optimization does not depend on other instructions than the IntPtr.get_Size call, so remove the checks that verify surrounding instructions. This makes the IntPtr.Size inliner kick in in more scenarios (such as the new tests). * Add tests. * [tests] Add mmp tests for optimizations. * [tests] Fix XM optimization tests. * [tests] Fix test build error.
2018-01-23 13:33:48 +03:00
// ILReader to parse the byte array provided by MethodBase.GetMethodBody ().GetILAsByteArray () into better-looking IL instructions.
//
// Authors:
// Rolf Bjarne Kvinge <rolf@xamarin.com>
//
// Copyright 2018 Microsoft Inc. All rights reserved.
//
// Adapted code from here: https://blogs.msdn.microsoft.com/haibo_luo/2005/10/04/read-il-from-methodbody/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace Linker.Shared
{
public class ILInstruction
{
public MethodBase Method;
public OpCode OpCode;
public int Offset;
public object Operand;
public ILInstruction (MethodBase method, int offset, OpCode opcode, object operand = null)
{
this.Method = method;
this.OpCode = opcode;
this.Offset = offset;
this.Operand = operand;
}
public override string ToString ()
{
var methodOperand = Operand as MethodBase;
if (methodOperand != null)
return $"IL_{Offset:0000} {OpCode} {methodOperand.DeclaringType.FullName}.{methodOperand.Name}";
return $"IL_{Offset:0000} {OpCode} {(Operand is MethodBase ? ((MethodBase) Operand).Name : Operand?.ToString ())}";
}
}
public class ILReader : IEnumerable<ILInstruction>
{
List<ILInstruction> instructions;
static OpCode [] oneByteOpcodes = new OpCode [0x100];
static OpCode [] twoByteOpcodes = new OpCode [0x100];
static ILReader ()
{
foreach (var fi in typeof (OpCodes).GetFields (BindingFlags.Public | BindingFlags.Static)) {
var opCode = (OpCode) fi.GetValue (null);
var value = (ushort) opCode.Value;
if (value < 0x100)
oneByteOpcodes [value] = opCode;
else if ((value & 0xff00) == 0xfe00)
twoByteOpcodes [value & 0xff] = opCode;
}
}
public ILReader (MethodBase method)
{
instructions = Parse (method);
}
IEnumerator<ILInstruction> IEnumerable<ILInstruction>.GetEnumerator ()
{
return instructions.GetEnumerator ();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
{
return instructions.GetEnumerator ();
}
List<ILInstruction> Parse (MethodBase method)
{
var rv = new List<ILInstruction> ();
var position = 0;
var body = method.GetMethodBody ();
if (body == null)
return rv;
var bytes = body.GetILAsByteArray ();
while (position < bytes.Length)
rv.Add (ReadInstruction (method, bytes, ref position));
return rv;
}
static ILInstruction ReadInstruction (MethodBase method, byte [] bytes, ref int position)
{
var offset = position;
var opCode = OpCodes.Nop;
// read first 1 or 2 bytes as opCode
Byte code = ReadByte (bytes, ref position);
if (code != 0xFE)
opCode = oneByteOpcodes [code];
else {
code = ReadByte (bytes, ref position);
opCode = twoByteOpcodes [code];
}
switch (opCode.OperandType) {
case OperandType.InlineNone:
return new ILInstruction (method, offset, opCode);
case OperandType.ShortInlineBrTarget:
return new ILInstruction (method, offset, opCode, ReadSByte (bytes, ref position));
case OperandType.InlineBrTarget:
return new ILInstruction (method, offset, opCode, ReadInt32 (bytes, ref position));
case OperandType.ShortInlineI:
return new ILInstruction (method, offset, opCode, ReadByte (bytes, ref position));
case OperandType.InlineI:
return new ILInstruction (method, offset, opCode, ReadInt32 (bytes, ref position));
case OperandType.InlineI8:
return new ILInstruction (method, offset, opCode, ReadInt64 (bytes, ref position));
case OperandType.ShortInlineR:
return new ILInstruction (method, offset, opCode, ReadSingle (bytes, ref position));
case OperandType.InlineR:
return new ILInstruction (method, offset, opCode, ReadDouble (bytes, ref position));
case OperandType.ShortInlineVar:
return new ILInstruction (method, offset, opCode, ReadByte (bytes, ref position));
case OperandType.InlineVar:
return new ILInstruction (method, offset, opCode, ReadUInt16 (bytes, ref position));
case OperandType.InlineString:
return new ILInstruction (method, offset, opCode, ReadInt32 (bytes, ref position));
case OperandType.InlineSig:
return new ILInstruction (method, offset, opCode, ReadInt32 (bytes, ref position));
case OperandType.InlineField:
return new ILInstruction (method, offset, opCode, ReadInt32 (bytes, ref position));
case OperandType.InlineType:
return new ILInstruction (method, offset, opCode, ReadInt32 (bytes, ref position));
case OperandType.InlineTok:
return new ILInstruction (method, offset, opCode, ReadInt32 (bytes, ref position));
case OperandType.InlineMethod:
return new ILInstruction (method, offset, opCode, method.Module.ResolveMethod (ReadInt32 (bytes, ref position)));
case OperandType.InlineSwitch:
var cases = ReadInt32 (bytes, ref position);
var deltas = new int [cases];
for (var i = 0; i < cases; i++)
deltas [i] = ReadInt32 (bytes, ref position);
return new ILInstruction (method, offset, opCode, deltas);
default:
throw new BadImageFormatException ("unexpected OperandType " + opCode.OperandType);
}
}
static byte ReadByte (byte[] bytes, ref int position)
{
return bytes [position++];
}
static sbyte ReadSByte (byte [] bytes, ref int position)
{
return (sbyte) ReadByte (bytes, ref position);
}
static ushort ReadUInt16 (byte [] bytes, ref int position)
{
position += 2;
return BitConverter.ToUInt16 (bytes, position - 2);
}
static uint ReadUInt32 (byte [] bytes, ref int position)
{
position += 4;
return BitConverter.ToUInt32 (bytes, position - 4);
}
static ulong ReadUInt64 (byte [] bytes, ref int position)
{
position += 8;
return BitConverter.ToUInt64 (bytes, position - 8);
}
static int ReadInt32 (byte [] bytes, ref int position)
{
position += 4;
return BitConverter.ToInt32 (bytes, position - 4);
}
static long ReadInt64 (byte [] bytes, ref int position)
{
position += 8;
return BitConverter.ToInt64 (bytes, position - 8);
}
static float ReadSingle (byte [] bytes, ref int position)
{
position += 4;
return BitConverter.ToSingle (bytes, position - 4);
}
static double ReadDouble (byte [] bytes, ref int position)
{
position += 8;
return BitConverter.ToDouble (bytes, position - 8);
}
}
}