2021-08-11 11:06:46 +03:00
|
|
|
using NUnit.Framework;
|
[linker] Add an well known candidate inliner substep along with tests (#1513)
TL&DR: This is *how* it should be done and tested, it's not complete
(single, simple case) nor the most interesting case ;-)
The trick is to make sure each case is covered by tests so a mono
_bump_ won't give us a BCL that does not conform to what the linker
expect.
What's the impact ?
1. There is the expected reduction of metadata in mscorlib. Since both
methods don't call other API there's no indirect effect (removal).
--- before 2017-01-15 11:12:44.000000000 -0500
+++ after 2017-01-15 11:12:56.000000000 -0500
@@ -13166,9 +13166,6 @@
System.Void System.Security.SecurityException::.ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)
System.Void System.Security.SecurityException::.ctor(System.String)
System.Void System.Security.SecurityException::GetObjectData(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)
-System.Boolean System.Security.SecurityManager::CheckElevatedPermissions()
-System.Security.SecurityManager
-System.Void System.Security.SecurityManager::EnsureElevatedPermissions()
System.Security.SecurityRulesAttribute
System.Security.SecurityRuleSet System.Security.SecurityRulesAttribute::m_ruleSet
System.Void System.Security.SecurityRulesAttribute::.ctor(System.Security.SecurityRuleSet)
2. There is no visible size change (even with #1) in mscorlib.dll due to
padding (compiler /filealign)
mscorlib.dll 793,600 793,600 0 0.00 %
3. there's a *very* small reduction of mscorlib.*.aotdata size
mscorlib.armv7.aotdata 717,264 717,216 -48 -0.01 %
mscorlib.arm64.aotdata 712,840 712,704 -136 -0.02 %
AOT data *.aotdata 6,460,064 6,459,880 -184 0.00 %
4. there's no change in executable size - normal as the AOT compiler has
_likely_ already doing the same optimization (before this commit)
Executable 29,270,272 29,270,272 0 0.00 %
Full comparison: https://gist.github.com/spouliot/0464c8fa3a92b6486dfd90595d9eb718
2017-01-18 05:49:44 +03:00
|
|
|
using System;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using Mono.Cecil;
|
|
|
|
using Mono.Cecil.Cil;
|
|
|
|
using Xamarin.Tests;
|
|
|
|
|
|
|
|
namespace Xamarin.Linker {
|
|
|
|
|
|
|
|
public static partial class Extensions {
|
|
|
|
|
|
|
|
// note: direct check, no inheritance
|
|
|
|
public static bool Is (this TypeReference type, string @namespace, string name)
|
|
|
|
{
|
|
|
|
return ((type != null) && (type.Name == name) && (type.Namespace == @namespace));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public abstract class InlinerTest {
|
|
|
|
|
|
|
|
// note: not every candidate should be handled by the linker
|
|
|
|
// most of them are _naturally_ eliminated by not being used/marked
|
|
|
|
static bool DisplayCandidates = false;
|
|
|
|
|
|
|
|
protected abstract string Assembly { get; }
|
|
|
|
|
|
|
|
AssemblyDefinition assembly;
|
|
|
|
|
|
|
|
protected virtual AssemblyDefinition AssemblyDefinition {
|
|
|
|
get {
|
|
|
|
if (assembly == null)
|
|
|
|
assembly = AssemblyDefinition.ReadAssembly (Assembly);
|
|
|
|
return assembly;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected string ListMethods (HashSet<string> h)
|
|
|
|
{
|
|
|
|
string result = Path.GetFileName (Assembly);
|
|
|
|
if (h.Count == 0)
|
|
|
|
return $" {result}: success";
|
|
|
|
return result + "\n" + h.Aggregate ((arg1, arg2) => arg1 + "\n" + arg2);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsCandidateForInlining (MethodDefinition m)
|
|
|
|
{
|
|
|
|
// candidates must be methods
|
|
|
|
if (m.IsConstructor)
|
|
|
|
return false;
|
|
|
|
// must have a body (IL)
|
|
|
|
if (!m.HasBody)
|
|
|
|
return false;
|
|
|
|
// must be static or not virtual (can't be overrriden)
|
|
|
|
if (!m.IsStatic && m.IsVirtual)
|
|
|
|
return false;
|
|
|
|
// the body must not have exception handlers or variables
|
|
|
|
var b = m.Body;
|
|
|
|
return !(b.HasExceptionHandlers || b.HasVariables || b.InitLocals);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// We look for candidates, without parameters, that only do `return true;`.
|
|
|
|
/// E.g. Such a static method can be inlined by replacing the `call` with a `ldc.i4.1` instruction
|
|
|
|
/// We must ensure that the list of methods we inline remains unchanged in the BCL we ship.
|
|
|
|
/// </summary>
|
|
|
|
protected void NoParameterReturnOnlyConstant (Code code, HashSet<string> list)
|
|
|
|
{
|
|
|
|
if (DisplayCandidates)
|
|
|
|
Console.WriteLine ($"### NoParameterReturnOnlyConstant {code}: {Path.GetFileName (Assembly)}");
|
|
|
|
|
|
|
|
var ad = AssemblyDefinition.ReadAssembly (Assembly);
|
|
|
|
foreach (var t in ad.MainModule.Types) {
|
|
|
|
if (!t.HasMethods)
|
|
|
|
continue;
|
|
|
|
foreach (var m in t.Methods) {
|
|
|
|
if (!IsCandidateForInlining (m))
|
|
|
|
continue;
|
|
|
|
if (m.HasParameters)
|
|
|
|
continue;
|
|
|
|
var b = m.Body;
|
|
|
|
if (b.Instructions.Count != 2)
|
|
|
|
continue;
|
|
|
|
var ins = b.Instructions [0];
|
|
|
|
if (code == ins.OpCode.Code) {
|
|
|
|
var s = m.ToString ();
|
|
|
|
list.Remove (s);
|
|
|
|
if (DisplayCandidates)
|
|
|
|
Console.WriteLine ($"* `{s}`");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// We look for candidates, without parameters and return value, that does nothing (only `ret`).
|
|
|
|
/// E.g. Such a static method can be inlined by replacing the `call` with a nop` instruction.
|
|
|
|
/// We must ensure that the list of methods we inline remains unchanged in the BCL we ship.
|
|
|
|
/// </summary>
|
|
|
|
protected void NoParameterNoReturnNoCode (HashSet<string> list)
|
|
|
|
{
|
|
|
|
if (DisplayCandidates)
|
|
|
|
Console.WriteLine ($"### ReturnOnly: {Path.GetFileName (Assembly)}");
|
|
|
|
|
|
|
|
foreach (var t in AssemblyDefinition.MainModule.Types) {
|
|
|
|
if (!t.HasMethods)
|
|
|
|
continue;
|
|
|
|
foreach (var m in t.Methods) {
|
|
|
|
if (!IsCandidateForInlining (m))
|
|
|
|
continue;
|
|
|
|
if (m.HasParameters)
|
|
|
|
continue;
|
|
|
|
if (!m.ReturnType.Is ("System", "Void"))
|
|
|
|
continue;
|
|
|
|
var b = m.Body;
|
|
|
|
if (b.Instructions.Count == 1) {
|
|
|
|
var s = m.ToString ();
|
|
|
|
list.Remove (s);
|
|
|
|
if (DisplayCandidates)
|
|
|
|
Console.WriteLine ($"* `{s}`");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[TestFixture]
|
|
|
|
public class MscorlibInlinerTest : InlinerTest {
|
|
|
|
|
|
|
|
protected override string Assembly {
|
|
|
|
get { return Path.Combine (Configuration.MonoTouchRootDirectory, "lib", "mono", "Xamarin.iOS", "mscorlib.dll"); }
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void True ()
|
|
|
|
{
|
|
|
|
// list MUST be kept in sync with InlinerSubStep.cs
|
|
|
|
var h = new HashSet<string> {
|
|
|
|
"System.Boolean System.Security.SecurityManager::CheckElevatedPermissions()",
|
|
|
|
};
|
|
|
|
NoParameterReturnOnlyConstant (Code.Ldc_I4_1, h);
|
|
|
|
Assert.That (h, Is.Empty, ListMethods (h));
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void Nop ()
|
|
|
|
{
|
|
|
|
// this list MUST be kept in sync with InlinerSubStep.cs
|
|
|
|
var h = new HashSet<string> {
|
|
|
|
"System.Void System.Security.SecurityManager::EnsureElevatedPermissions()",
|
|
|
|
};
|
|
|
|
NoParameterNoReturnNoCode (h);
|
|
|
|
Assert.That (h, Is.Empty, ListMethods (h));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|