From d4792dc98dfca29849bedc21519d9cf9ae6f2cb6 Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Wed, 14 Dec 2016 13:25:42 +0100 Subject: [XamlC] Allow compilation of IValueProviders (#622) * [XamlC] Allow compilation of IValueProviders `IValueProvider`s tagged with the appropriate attribute will be bypassed and the compiled version, if found, will be used. This first version contains a compiled version of Setter's IValueProvider and it already reduces the amount of generated IL by 39% in, e.g. StyleTests. It's a huge gain because XamlC no longer have to generate ServiceProviders for those, so the methodbody is smaller, takes less time to jit, less time to execute and nothing is invoked at runtime, which probably saves a tons of time as well, as most IValueProvider implementation heavily uses reflection. * name bool parameters --- .../BindablePropertyReferenceExtensions.cs | 4 +-- .../BindablePropertyConverter.cs | 7 +++- .../ICompiledValueProvider.cs | 13 +++++++ .../CompiledValueProviders/SetterValueProvider.cs | 40 ++++++++++++++++++++++ Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs | 24 +++++++++++-- .../Xamarin.Forms.Build.Tasks.csproj | 3 ++ Xamarin.Forms.Core/Setter.cs | 1 + 7 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 Xamarin.Forms.Build.Tasks/CompiledValueProviders/ICompiledValueProvider.cs create mode 100644 Xamarin.Forms.Build.Tasks/CompiledValueProviders/SetterValueProvider.cs diff --git a/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs index 159bbf5f..5f1826db 100644 --- a/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs @@ -26,7 +26,7 @@ namespace Xamarin.Forms.Build.Tasks md.IsStatic && md.IsPublic && md.Parameters.Count == 1 && - md.Parameters [0].ParameterType.FullName == "Xamarin.Forms.BindableObject", module).SingleOrDefault()?.Item1; + md.Parameters[0].ParameterType.InheritsFromOrImplements(module.Import(typeof(BindableObject))), module).SingleOrDefault()?.Item1; if (getter == null) throw new XamlParseException($"Missing a public static Get{bpName} or a public instance property getter for the attached property \"{bpRef.DeclaringType}.{bpRef.Name}\"", iXmlLineInfo); @@ -43,7 +43,7 @@ namespace Xamarin.Forms.Build.Tasks md.IsStatic && md.IsPublic && md.Parameters.Count == 1 && - md.Parameters [0].ParameterType.FullName == "Xamarin.Forms.BindableObject", module).SingleOrDefault()?.Item1; + md.Parameters[0].ParameterType.InheritsFromOrImplements(module.Import(typeof(BindableObject))), module).SingleOrDefault()?.Item1; var attributes = new List(); if (property != null && property.HasCustomAttributes) diff --git a/Xamarin.Forms.Build.Tasks/CompiledConverters/BindablePropertyConverter.cs b/Xamarin.Forms.Build.Tasks/CompiledConverters/BindablePropertyConverter.cs index 92a53539..c1d11487 100644 --- a/Xamarin.Forms.Build.Tasks/CompiledConverters/BindablePropertyConverter.cs +++ b/Xamarin.Forms.Build.Tasks/CompiledConverters/BindablePropertyConverter.cs @@ -18,7 +18,12 @@ namespace Xamarin.Forms.Core.XamlC yield return Instruction.Create(OpCodes.Ldnull); yield break; } + var bpRef = GetBindablePropertyFieldReference(value, module, node); + yield return Instruction.Create(OpCodes.Ldsfld, bpRef); + } + public FieldReference GetBindablePropertyFieldReference(string value, ModuleDefinition module, BaseNode node) + { FieldReference bpRef = null; string typeName = null, propertyName = null; @@ -49,7 +54,7 @@ namespace Xamarin.Forms.Core.XamlC bpRef = GetBindablePropertyFieldReference(typeRef, propertyName, module); if (bpRef == null) throw new XamlParseException($"Can't resolve {propertyName} on {typeRef.Name}", node); - yield return Instruction.Create(OpCodes.Ldsfld, bpRef); + return bpRef; } public static TypeReference GetTypeReference(string xmlType, ModuleDefinition module, BaseNode iNode) diff --git a/Xamarin.Forms.Build.Tasks/CompiledValueProviders/ICompiledValueProvider.cs b/Xamarin.Forms.Build.Tasks/CompiledValueProviders/ICompiledValueProvider.cs new file mode 100644 index 00000000..37418bd9 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/CompiledValueProviders/ICompiledValueProvider.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Xamarin.Forms.Build.Tasks; + +namespace Xamarin.Forms.Xaml +{ + interface ICompiledValueProvider + { + IEnumerable ProvideValue(VariableDefinitionReference vardefref, ModuleDefinition module, BaseNode node, ILContext context); + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/CompiledValueProviders/SetterValueProvider.cs b/Xamarin.Forms.Build.Tasks/CompiledValueProviders/SetterValueProvider.cs new file mode 100644 index 00000000..163bd9e7 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/CompiledValueProviders/SetterValueProvider.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +using Mono.Cecil; +using Mono.Cecil.Cil; + +using Xamarin.Forms.Xaml; +using Xamarin.Forms.Build.Tasks; + +namespace Xamarin.Forms.Core.XamlC +{ + class SetterValueProvider : ICompiledValueProvider + { + public IEnumerable ProvideValue(VariableDefinitionReference vardefref, ModuleDefinition module, BaseNode node, ILContext context) + { + var valueNode = ((IElementNode)node).Properties[new XmlName("", "Value")]; + + //if it's an elementNode, there's probably no need to convert it + if (valueNode is IElementNode) + yield break; + + var value = ((string)((ValueNode)valueNode).Value); + var bpNode = ((ValueNode)((IElementNode)node).Properties[new XmlName("", "Property")]); + var bpRef = (new BindablePropertyConverter()).GetBindablePropertyFieldReference((string)bpNode.Value, module, bpNode); + + TypeReference _; + var setValueRef = module.Import(module.Import(typeof(Setter)).GetProperty(p => p.Name == "Value", out _).SetMethod); + + //push the setter + yield return Instruction.Create(OpCodes.Ldloc, vardefref.VariableDefinition); + + //push the value + foreach (var instruction in ((ValueNode)valueNode).PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context, bpRef: bpRef), boxValueTypes: true, unboxValueTypes: false)) + yield return instruction; + + //set the value + yield return Instruction.Create(OpCodes.Callvirt, setValueRef); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs index 6a7a3bef..9cf62221 100644 --- a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs @@ -242,7 +242,8 @@ namespace Xamarin.Forms.Build.Tasks } public static IEnumerable ProvideValue(VariableDefinitionReference vardefref, ILContext context, - ModuleDefinition module, ElementNode node, FieldReference bpRef = null, PropertyReference propertyRef = null, TypeReference propertyDeclaringTypeRef = null) + ModuleDefinition module, ElementNode node, FieldReference bpRef = null, + PropertyReference propertyRef = null, TypeReference propertyDeclaringTypeRef = null) { GenericInstanceType markupExtension; IList genericArguments; @@ -306,8 +307,25 @@ namespace Xamarin.Forms.Build.Tasks } else if (context.Variables[node].VariableType.ImplementsInterface(module.Import(typeof (IValueProvider)))) { - var markExt = module.Import(typeof (IValueProvider)).Resolve(); - var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue"); + var valueProviderType = context.Variables[node].VariableType; + //If the IValueProvider has a ProvideCompiledAttribute that can be resolved, shortcut this + var compiledValueProviderName = valueProviderType?.GetCustomAttribute(module.Import(typeof(ProvideCompiledAttribute)))?.ConstructorArguments?[0].Value as string; + Type compiledValueProviderType; + if (compiledValueProviderName != null && (compiledValueProviderType = Type.GetType(compiledValueProviderName)) != null) { + var compiledValueProvider = Activator.CreateInstance(compiledValueProviderType); + var cProvideValue = typeof(ICompiledValueProvider).GetMethods().FirstOrDefault(md => md.Name == "ProvideValue"); + var instructions = (IEnumerable)cProvideValue.Invoke(compiledValueProvider, new object[] { + vardefref, + context.Body.Method.Module, + node as BaseNode, + context}); + foreach (var i in instructions) + yield return i; + yield break; + } + + var valueProviderDef = module.Import(typeof (IValueProvider)).Resolve(); + var provideValueInfo = valueProviderDef.Methods.First(md => md.Name == "ProvideValue"); var provideValue = module.Import(provideValueInfo); vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object); diff --git a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj index ef10720d..51dd60be 100644 --- a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj +++ b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj @@ -105,6 +105,8 @@ + + @@ -128,6 +130,7 @@ + diff --git a/Xamarin.Forms.Core/Setter.cs b/Xamarin.Forms.Core/Setter.cs index b260e8f0..ea58d4fd 100644 --- a/Xamarin.Forms.Core/Setter.cs +++ b/Xamarin.Forms.Core/Setter.cs @@ -8,6 +8,7 @@ using Xamarin.Forms.Xaml; namespace Xamarin.Forms { [ContentProperty("Value")] + [ProvideCompiled("Xamarin.Forms.Core.XamlC.SetterValueProvider")] public sealed class Setter : IValueProvider { readonly ConditionalWeakTable _originalValues = new ConditionalWeakTable(); -- cgit v1.2.3