Skip to content

Commit

Permalink
Trimmable runtime xaml loader (#12937)
Browse files Browse the repository at this point in the history
* Add trimming attributes to the Runtime XAML Loader project

* Create CompilerDynamicDependenciesAttribute with all known dynamic types

* Use master XamlX

* Implement CompilerDynamicDependenciesGenerator to simplify CompilerDynamicDependencies attribute

* Better formatting of generated code

* Be safe about type converters as well

* Remove unnecessary warning

* Also include FindType just in case
  • Loading branch information
maxkatz6 authored and danwalmsley committed Oct 6, 2023
1 parent a9f7e9c commit 2bfb008
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<IsPackable>true</IsPackable>
<DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
<DefineConstants>$(DefineConstants);XAMLX_INTERNAL;XAML_RUNTIME_LOADER</DefineConstants>
</PropertyGroup>
<!--Disable Net Perf. analyzer for submodule to avoid commit issue -->
<PropertyGroup>
Expand All @@ -21,4 +21,6 @@
<ProjectReference Include="..\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\TrimmingEnable.props" />
<Import Project="..\..\..\build\SourceGenerators.props" />
</Project>
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Text;
using Avalonia.Markup.Xaml.XamlIl;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;

#nullable enable
namespace Avalonia.Markup.Xaml
{
Expand All @@ -18,6 +21,7 @@ public static class AvaloniaRuntimeXamlLoader
/// <param name="uri">The URI of the XAML being loaded.</param>
/// <param name="designMode">Indicates whether the XAML is being loaded in design mode.</param>
/// <returns>The loaded object.</returns>
[RequiresUnreferencedCode(XamlX.TrimmingMessages.DynamicXamlReference)]
public static object Load(string xaml, Assembly? localAssembly = null, object? rootInstance = null, Uri? uri = null, bool designMode = false)
{
xaml = xaml ?? throw new ArgumentNullException(nameof(xaml));
Expand All @@ -37,6 +41,7 @@ public static object Load(string xaml, Assembly? localAssembly = null, object? r
/// <param name="uri">The URI of the XAML being loaded.</param>
/// <param name="designMode">Indicates whether the XAML is being loaded in design mode.</param>
/// <returns>The loaded object.</returns>
[RequiresUnreferencedCode(XamlX.TrimmingMessages.DynamicXamlReference)]
public static object Load(Stream stream, Assembly? localAssembly = null, object? rootInstance = null, Uri? uri = null,
bool designMode = false)
=> AvaloniaXamlIlRuntimeCompiler.Load(new RuntimeXamlLoaderDocument(uri, rootInstance, stream),
Expand All @@ -48,6 +53,7 @@ public static object Load(Stream stream, Assembly? localAssembly = null, object?
/// <param name="document">The stream containing the XAML.</param>
/// <param name="configuration">Xaml loader configuration.</param>
/// <returns>The loaded object.</returns>
[RequiresUnreferencedCode(XamlX.TrimmingMessages.DynamicXamlReference)]
public static object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration? configuration = null)
=> AvaloniaXamlIlRuntimeCompiler.Load(document, configuration ?? new RuntimeXamlLoaderConfiguration());

Expand All @@ -57,6 +63,7 @@ public static object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderC
/// <param name="documents">Collection of documents.</param>
/// <param name="configuration">Xaml loader configuration.</param>
/// <returns>The loaded objects per each input document. If document was removed, the element by index is null.</returns>
[RequiresUnreferencedCode(XamlX.TrimmingMessages.DynamicXamlReference)]
public static IReadOnlyList<object?> LoadGroup(IReadOnlyCollection<RuntimeXamlLoaderDocument> documents, RuntimeXamlLoaderConfiguration? configuration = null)
=> AvaloniaXamlIlRuntimeCompiler.LoadGroup(documents, configuration ?? new RuntimeXamlLoaderConfiguration());

Expand All @@ -66,6 +73,7 @@ public static object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderC
/// <param name="xaml">The string containing the XAML.</param>
/// <param name="localAssembly">Default assembly for clr-namespace:.</param>
/// <returns>The loaded object.</returns>
[RequiresUnreferencedCode(XamlX.TrimmingMessages.DynamicXamlReference)]
public static object Parse(string xaml, Assembly? localAssembly = null)
=> Load(xaml, localAssembly);

Expand All @@ -76,8 +84,8 @@ public static object Parse(string xaml, Assembly? localAssembly = null)
/// <param name="xaml">>The string containing the XAML.</param>
/// <param name="localAssembly">>Default assembly for clr-namespace:.</param>
/// <returns>The loaded object.</returns>
public static T Parse<T>(string xaml, Assembly? localAssembly = null)
[RequiresUnreferencedCode(XamlX.TrimmingMessages.DynamicXamlReference)]
public static T Parse<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string xaml, Assembly? localAssembly = null)
=> (T)Parse(xaml, localAssembly);

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
Expand All @@ -11,6 +12,7 @@
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Platform;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
Expand All @@ -22,9 +24,13 @@
using XamlX.Ast;
using XamlX.IL.Cecil;
#endif

namespace Avalonia.Markup.Xaml.XamlIl
{
static class AvaloniaXamlIlRuntimeCompiler
#if !RUNTIME_XAML_CECIL
[RequiresUnreferencedCode(XamlX.TrimmingMessages.DynamicXamlReference)]
#endif
internal static class AvaloniaXamlIlRuntimeCompiler
{
#if !RUNTIME_XAML_CECIL
private static SreTypeSystem _sreTypeSystem;
Expand All @@ -37,6 +43,7 @@ static class AvaloniaXamlIlRuntimeCompiler
private static AssemblyBuilder _sreAsm;
private static bool _sreCanSave;

[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = XamlX.TrimmingMessages.CanBeSafelyTrimmed)]
public static void DumpRuntimeCompilationResults()
{
if (_sreBuilder == null)
Expand All @@ -55,7 +62,9 @@ public static void DumpRuntimeCompilationResults()
//Ignore
}
}


[CompilerDynamicDependencies]
[UnconditionalSuppressMessage("Trimming", "IL2072", Justification = XamlX.TrimmingMessages.GeneratedTypes)]
static void InitializeSre()
{
if (_sreTypeSystem == null)
Expand All @@ -66,10 +75,7 @@ static void InitializeSre()
var name = new AssemblyName(Guid.NewGuid().ToString("N"));
if (_sreCanSave)
{
var define = AppDomain.CurrentDomain.GetType().GetMethods()
.FirstOrDefault(m => m.Name == "DefineDynamicAssembly"
&& m.GetParameters().Length == 3 &&
m.GetParameters()[2].ParameterType == typeof(string));
var define = GetDefineDynamicAssembly();
if (define != null)
_sreAsm = (AssemblyBuilder)define.Invoke(AppDomain.CurrentDomain, new object[]
{
Expand Down Expand Up @@ -101,6 +107,12 @@ static void InitializeSre()
_ignoresAccessChecksFromAttribute = EmitIgnoresAccessCheckAttributeDefinition(_sreBuilder);
}

[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = XamlX.TrimmingMessages.CanBeSafelyTrimmed)]
static MethodInfo GetDefineDynamicAssembly() => AppDomain.CurrentDomain.GetType().GetMethods()
.FirstOrDefault(m => m.Name == "DefineDynamicAssembly"
&& m.GetParameters().Length == 3 &&
m.GetParameters()[2].ParameterType == typeof(string));

static Type EmitIgnoresAccessCheckAttributeDefinition(ModuleBuilder builder)
{
var tb = builder.DefineType("System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute",
Expand Down Expand Up @@ -138,6 +150,7 @@ static Type EmitIgnoresAccessCheckAttributeDefinition(ModuleBuilder builder)
return tb.CreateTypeInfo();
}

[UnconditionalSuppressMessage("Trimming", "IL2080", Justification = XamlX.TrimmingMessages.GeneratedTypes)]
static void EmitIgnoresAccessCheckToAttribute(AssemblyName assemblyName)
{
var name = assemblyName.Name;
Expand All @@ -150,7 +163,6 @@ static void EmitIgnoresAccessCheckToAttribute(AssemblyName assemblyName)
_ignoresAccessChecksFromAttribute.GetConstructors()[0],
new object[] { name }));
}


static object LoadSre(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
{
Expand Down Expand Up @@ -184,7 +196,8 @@ static IReadOnlyList<object> LoadGroupSre(IReadOnlyCollection<RuntimeXamlLoaderD
DumpRuntimeCompilationResults();
}
}


[UnconditionalSuppressMessage("Trimming", "IL2072", Justification = XamlX.TrimmingMessages.GeneratedTypes)]
static IReadOnlyList<object> LoadGroupSreCore(IReadOnlyCollection<RuntimeXamlLoaderDocument> documents, RuntimeXamlLoaderConfiguration configuration)
{
InitializeSre();
Expand Down Expand Up @@ -264,14 +277,16 @@ document.RootInstance is null ?
.Select(t => LoadOrPopulate(t.Item1, t.Item2.RootInstance, t.Item2.ServiceProvider))
.ToArray();
}

static object LoadSreCore(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
{
return LoadGroupSreCore(new[] { document }, configuration).Single();
}
#endif

static object LoadOrPopulate(Type created, object rootInstance, IServiceProvider parentServiceProvider)
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = XamlX.TrimmingMessages.GeneratedTypes)]
[UnconditionalSuppressMessage("Trimming", "IL2072", Justification = XamlX.TrimmingMessages.GeneratedTypes)]
static object LoadOrPopulate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type created, object rootInstance, IServiceProvider parentServiceProvider)
{
var isp = Expression.Parameter(typeof(IServiceProvider));

Expand Down Expand Up @@ -317,7 +332,7 @@ static object LoadOrPopulate(Type created, object rootInstance, IServiceProvider
return rootInstance;
}
}

public static object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
{
#if RUNTIME_XAML_CECIL
Expand Down Expand Up @@ -364,6 +379,7 @@ private static string GetSafeUriIdentifier(Uri uri)
private static XamlIlXmlnsMappings _cecilXmlns;
private static bool _cecilInitialized;

[CompilerDynamicDependencies]
static void InitializeCecil()
{
if(_cecilInitialized)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Diagnostics.CodeAnalysis;

// WARNING: this is a partial class, second part of which is generated by CompilerDynamicDependenciesGenerator.

namespace Avalonia.Markup.Xaml.XamlIl;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
internal sealed partial class CompilerDynamicDependenciesAttribute : Attribute
{
public const DynamicallyAccessedMemberTypes XamlDynamicallyAccessedMemberTypes = DynamicallyAccessedMemberTypes.All ^
(DynamicallyAccessedMemberTypes.NonPublicConstructors
| DynamicallyAccessedMemberTypes.NonPublicEvents
| DynamicallyAccessedMemberTypes.NonPublicFields
| DynamicallyAccessedMemberTypes.NonPublicMethods
| DynamicallyAccessedMemberTypes.NonPublicProperties
| DynamicallyAccessedMemberTypes.NonPublicNestedTypes);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
Expand Down Expand Up @@ -143,6 +144,9 @@ public void TransformGroup(IReadOnlyCollection<IXamlDocumentResource> documents,
}
}

#if !XAMLX_CECIL_INTERNAL
[RequiresUnreferencedCode(XamlX.TrimmingMessages.DynamicXamlReference)]
#endif
public XamlDocument Parse(string xaml, IXamlType overrideRootType)
{
var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
Expand Down Expand Up @@ -191,7 +195,10 @@ public void Compile(XamlDocument document, XamlDocumentTypeBuilderProvider typeB
(s, returnType, parameters) => tb.DefineDelegateSubType(s, false, returnType, parameters), baseUri,
fileSource);
}


#if !XAMLX_CECIL_INTERNAL
[RequiresUnreferencedCode(XamlX.TrimmingMessages.DynamicXamlReference)]
#endif
public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder<IXamlILEmitter> tb, IXamlType overrideRootType)
{
var parsed = Parse(xaml, overrideRootType);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Avalonia.Controls;
Expand Down Expand Up @@ -120,21 +121,18 @@ public AttributeResolver(IXamlTypeSystem typeSystem, XamlLanguageTypeMappings ma

void AddType(IXamlType type, IXamlType conv)
=> _converters.Add(new KeyValuePair<IXamlType, IXamlType>(type, conv));

void Add(string type, string conv)
=> AddType(typeSystem.GetType(type), typeSystem.GetType(conv));

Add("Avalonia.Media.IImage","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter");
Add("Avalonia.Media.Imaging.Bitmap","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter");
Add("Avalonia.Media.IImageBrushSource","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter");
AddType(typeSystem.GetType("Avalonia.Media.IImage"), typeSystem.GetType("Avalonia.Markup.Xaml.Converters.BitmapTypeConverter"));
AddType(typeSystem.GetType("Avalonia.Media.Imaging.Bitmap"), typeSystem.GetType("Avalonia.Markup.Xaml.Converters.BitmapTypeConverter"));
AddType(typeSystem.GetType("Avalonia.Media.IImageBrushSource"), typeSystem.GetType("Avalonia.Markup.Xaml.Converters.BitmapTypeConverter"));
var ilist = typeSystem.GetType("System.Collections.Generic.IList`1");
AddType(ilist.MakeGenericType(typeSystem.GetType("Avalonia.Point")),
typeSystem.GetType("Avalonia.Markup.Xaml.Converters.PointsListTypeConverter"));
Add("Avalonia.Controls.WindowIcon","Avalonia.Markup.Xaml.Converters.IconTypeConverter");
Add("System.Globalization.CultureInfo", "System.ComponentModel.CultureInfoConverter");
Add("System.Uri", "Avalonia.Markup.Xaml.Converters.AvaloniaUriTypeConverter");
Add("System.TimeSpan", "Avalonia.Markup.Xaml.Converters.TimeSpanTypeConverter");
Add("Avalonia.Media.FontFamily","Avalonia.Markup.Xaml.Converters.FontFamilyTypeConverter");
AddType(typeSystem.GetType("Avalonia.Controls.WindowIcon"), typeSystem.GetType("Avalonia.Markup.Xaml.Converters.IconTypeConverter"));
AddType(typeSystem.GetType("System.Globalization.CultureInfo"), typeSystem.GetType( "System.ComponentModel.CultureInfoConverter"));
AddType(typeSystem.GetType("System.Uri"), typeSystem.GetType( "Avalonia.Markup.Xaml.Converters.AvaloniaUriTypeConverter"));
AddType(typeSystem.GetType("System.TimeSpan"), typeSystem.GetType( "Avalonia.Markup.Xaml.Converters.TimeSpanTypeConverter"));
AddType(typeSystem.GetType("Avalonia.Media.FontFamily"), typeSystem.GetType("Avalonia.Markup.Xaml.Converters.FontFamilyTypeConverter"));
_avaloniaList = typeSystem.GetType("Avalonia.Collections.AvaloniaList`1");
_avaloniaListConverter = typeSystem.GetType("Avalonia.Collections.AvaloniaListConverter`1");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
using XamlX.Emit;
Expand Down Expand Up @@ -203,7 +204,7 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
ThemeVariant = cfg.TypeSystem.GetType("Avalonia.Styling.ThemeVariant");
WindowTransparencyLevel = cfg.TypeSystem.GetType("Avalonia.Controls.WindowTransparencyLevel");

(IXamlType, IXamlConstructor) GetNumericTypeInfo(string name, IXamlType componentType, int componentCount)
(IXamlType, IXamlConstructor) GetNumericTypeInfo([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] string name, IXamlType componentType, int componentCount)
{
var type = cfg.TypeSystem.GetType(name);
var ctor = type.GetConstructor(Enumerable.Range(0, componentCount).Select(_ => componentType).ToList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<ItemGroup>
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerExtensions\**\*.cs" />
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\AvaloniaXamlIlRuntimeCompiler.cs" />
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerDynamicDependencies.cs" />
<Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
3 changes: 2 additions & 1 deletion src/tools/Avalonia.Generators/Avalonia.Generators.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
<Compile Link="Compiler\XamlX\filename" Include="../../Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/src/XamlX/**/*.cs" />
<Compile Remove="../../Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/src/XamlX/obj/**/*.cs" />
<Compile Remove="../../Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/src/XamlX/**/SreTypeSystem.cs" />
<Compile Include="..\..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
<Compile Include="..\..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" Visible="False" />
<Compile Include="..\..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit 2bfb008

Please sign in to comment.