-
Notifications
You must be signed in to change notification settings - Fork 127
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new optimization steps to make Mark step more effective
Two new steps have been introduced BodySubstituterStep This step removes any conditional blocks when the condition or conditions are evaluated as constants. This step does not do any inlining. The conditional logic is kept but based on the known values only one branch is always taken. A simple example which can be detected by linker. ```c# class C { static void Main () { if (Result) Console.WriteLine (); // <- this call will be marked and removed } static bool Result () => false; } ``` RemoveUnreachableBlocksStep A new command-line option called `--substitutions` allow external customization of any methoda for assemblies which are linked. The syntax is same as for existing linker descriptor XML files but it add way to control body modifications. An example of XML file ```xml <linker> <assembly fullname="test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> <type fullname="Mono.Linker.Tests.Cases.Substitutions.StubBodyWithValue"> <method signature="System.String TestMethod_1()" body="stub" value="abcd"> </method> </type> </assembly> </linker> ``` The `value` attribute is optional and only required when the method stub should not fallback to default behaviour. Additional to `stub` value also `remove` value is supported to forcibly remove body of the method when the method is marked. This is useful when the conditional logic cannot be evaluated by linker and the method will be marked but never actually reached. Applicability Both steps can be combined to achieve the effect of externally customizing which conditional branches can be removed during the linking. I can illustrate that on IntPtr.Size property. With following substitution any code that has compiled in conditional logic for 64 bits handling by checking IntPtr.Size will be removed during linking. ```xml <linker> <assembly fullname="mscorlib"> <type fullname="System.IntPtr"> <method signature="System.Int32 get_Size()" body="stub" value="4"> </method> </type> </assembly> </linker> ``` Implements #752
- Loading branch information
1 parent
8f948ea
commit 78965be
Showing
37 changed files
with
2,393 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
using System; | ||
using System.IO; | ||
using System.Xml.XPath; | ||
using Mono.Cecil; | ||
using System.Globalization; | ||
|
||
namespace Mono.Linker.Steps | ||
{ | ||
public class BodySubstituterStep : BaseStep | ||
{ | ||
protected override void Process () | ||
{ | ||
var files = Context.Substitutions; | ||
if (files == null) | ||
return; | ||
|
||
foreach (var file in files) { | ||
try { | ||
ReadSubstitutionFile (GetSubstitutions (file)); | ||
} catch (Exception ex) when (!(ex is XmlResolutionException)) { | ||
throw new XmlResolutionException ($"Failed to process XML substitution '{file}'", ex); | ||
} | ||
|
||
} | ||
} | ||
|
||
static XPathDocument GetSubstitutions (string substitutionsFile) | ||
{ | ||
using (FileStream fs = File.OpenRead (substitutionsFile)) { | ||
return GetSubstitutions (fs); | ||
} | ||
} | ||
|
||
static XPathDocument GetSubstitutions (Stream substitutions) | ||
{ | ||
using (StreamReader sr = new StreamReader (substitutions)) { | ||
return new XPathDocument (sr); | ||
} | ||
} | ||
|
||
void ReadSubstitutionFile (XPathDocument document) | ||
{ | ||
XPathNavigator nav = document.CreateNavigator (); | ||
|
||
// Initial structure check | ||
if (!nav.MoveToChild ("linker", "")) | ||
return; | ||
|
||
// TODO: Add handling for feature | ||
|
||
ProcessAssemblies (nav.SelectChildren ("assembly", "")); | ||
} | ||
|
||
void ProcessAssemblies (XPathNodeIterator iterator) | ||
{ | ||
while (iterator.MoveNext ()) { | ||
var name = GetAssemblyName (iterator.Current); | ||
|
||
var cache = Context.Resolver.AssemblyCache; | ||
|
||
if (!cache.TryGetValue (name.Name, out AssemblyDefinition assembly)) { | ||
Context.LogMessage (MessageImportance.Low, $"Could not match assembly '{name.FullName}' for substitution"); | ||
continue; | ||
} | ||
|
||
ProcessAssembly (assembly, iterator); | ||
} | ||
} | ||
|
||
void ProcessAssembly (AssemblyDefinition assembly, XPathNodeIterator iterator) | ||
{ | ||
ProcessTypes (assembly, iterator.Current.SelectChildren ("type", "")); | ||
} | ||
|
||
void ProcessTypes (AssemblyDefinition assembly, XPathNodeIterator iterator) | ||
{ | ||
while (iterator.MoveNext ()) { | ||
XPathNavigator nav = iterator.Current; | ||
|
||
string fullname = GetAttribute (nav, "fullname"); | ||
|
||
TypeDefinition type = assembly.MainModule.GetType (fullname); | ||
|
||
if (type == null) { | ||
Context.LogMessage (MessageImportance.Low, $"Could not resolve type '{fullname}' for substitution"); | ||
continue; | ||
} | ||
|
||
ProcessType (type, nav); | ||
} | ||
} | ||
|
||
void ProcessType (TypeDefinition type, XPathNavigator nav) | ||
{ | ||
if (!nav.HasChildren) | ||
return; | ||
|
||
XPathNodeIterator methods = nav.SelectChildren ("method", ""); | ||
if (methods.Count == 0) | ||
return; | ||
|
||
ProcessMethods (type, methods); | ||
} | ||
|
||
void ProcessMethods (TypeDefinition type, XPathNodeIterator iterator) | ||
{ | ||
while (iterator.MoveNext ()) | ||
ProcessMethod (type, iterator); | ||
} | ||
|
||
void ProcessMethod (TypeDefinition type, XPathNodeIterator iterator) | ||
{ | ||
string signature = GetAttribute (iterator.Current, "signature"); | ||
if (string.IsNullOrEmpty (signature)) | ||
return; | ||
|
||
MethodDefinition method = FindMethod (type, signature); | ||
if (method == null) { | ||
Context.LogMessage (MessageImportance.Low, $"Could not find method '{signature}' for substitution"); | ||
return; | ||
} | ||
|
||
string action = GetAttribute (iterator.Current, "body"); | ||
switch (action) { | ||
case "remove": | ||
Annotations.SetAction (method, MethodAction.ConvertToThrow); | ||
return; | ||
case "stub": | ||
string value = GetAttribute (iterator.Current, "value"); | ||
if (value != "") { | ||
if (!TryConvertValue (value, method.ReturnType, out object res)) { | ||
Context.LogMessage (MessageImportance.High, $"Invalid value for '{signature}' stub"); | ||
return; | ||
} | ||
|
||
Annotations.SetMethodStubValue (method, res); | ||
} | ||
|
||
Annotations.SetAction (method, MethodAction.ConvertToStub); | ||
return; | ||
default: | ||
Context.LogMessage (MessageImportance.High, $"Unknown body modification '{action}' for '{signature}'"); | ||
return; | ||
} | ||
} | ||
|
||
static bool TryConvertValue (string value, TypeReference target, out object result) | ||
{ | ||
switch (target.MetadataType) { | ||
case MetadataType.Boolean: | ||
if (bool.TryParse (value, out bool bvalue)) { | ||
result = bvalue ? 1 : 0; | ||
return true; | ||
} | ||
|
||
goto case MetadataType.Int32; | ||
|
||
case MetadataType.Byte: | ||
if (!byte.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out byte byteresult)) | ||
break; | ||
|
||
result = (int) byteresult; | ||
return true; | ||
|
||
case MetadataType.SByte: | ||
if (!sbyte.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out sbyte sbyteresult)) | ||
break; | ||
|
||
result = (int) sbyteresult; | ||
return true; | ||
|
||
case MetadataType.Int16: | ||
if (!short.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out short shortresult)) | ||
break; | ||
|
||
result = (int) shortresult; | ||
return true; | ||
|
||
case MetadataType.UInt16: | ||
if (!ushort.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out ushort ushortresult)) | ||
break; | ||
|
||
result = (int) ushortresult; | ||
return true; | ||
|
||
case MetadataType.Int32: | ||
case MetadataType.UInt32: | ||
if (!int.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int iresult)) | ||
break; | ||
|
||
result = iresult; | ||
return true; | ||
|
||
case MetadataType.Double: | ||
if (!double.TryParse (value, NumberStyles.Float, CultureInfo.InvariantCulture, out double dresult)) | ||
break; | ||
|
||
result = dresult; | ||
return true; | ||
|
||
case MetadataType.Single: | ||
if (!float.TryParse (value, NumberStyles.Float, CultureInfo.InvariantCulture, out float fresult)) | ||
break; | ||
|
||
result = fresult; | ||
return true; | ||
|
||
case MetadataType.Int64: | ||
case MetadataType.UInt64: | ||
if (!long.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out long lresult)) | ||
break; | ||
|
||
result = lresult; | ||
return true; | ||
|
||
case MetadataType.Char: | ||
if (!char.TryParse (value, out char chresult)) | ||
break; | ||
|
||
result = (int) chresult; | ||
return true; | ||
|
||
case MetadataType.String: | ||
if (value is string || value == null) { | ||
result = value; | ||
return true; | ||
} | ||
|
||
break; | ||
} | ||
|
||
result = null; | ||
return false; | ||
} | ||
|
||
static MethodDefinition FindMethod (TypeDefinition type, string signature) | ||
{ | ||
if (!type.HasMethods) | ||
return null; | ||
|
||
foreach (MethodDefinition meth in type.Methods) | ||
if (signature == ResolveFromXmlStep.GetMethodSignature (meth, includeGenericParameters: true)) | ||
return meth; | ||
|
||
return null; | ||
} | ||
|
||
static AssemblyNameReference GetAssemblyName (XPathNavigator nav) | ||
{ | ||
return AssemblyNameReference.Parse (GetAttribute (nav, "fullname")); | ||
} | ||
|
||
static string GetAttribute (XPathNavigator nav, string attribute) | ||
{ | ||
return nav.GetAttribute (attribute, ""); | ||
} | ||
} | ||
} |
Oops, something went wrong.